diff --git a/app/assets/javascripts/fakeJamClient.js b/app/assets/javascripts/fakeJamClient.js new file mode 100644 index 000000000..60fb1ca2b --- /dev/null +++ b/app/assets/javascripts/fakeJamClient.js @@ -0,0 +1,21 @@ +(function(context,$) { + + context.JK = context.JK || {}; + context.JK.FakeJamClient = function(app) { + var logger = context.JK.logger; + logger.info("*** Fake JamClient instance initialized. ***"); + + function testLatency(client, callback) { + var response = { + id: client.id, + latency: 50 + }; + callback(response); + } + + // Javascript Bridge seems to camel-case + this.TestLatency = testLatency; + + }; + + })(window,jQuery); \ No newline at end of file diff --git a/app/assets/javascripts/findSession.js b/app/assets/javascripts/findSession.js index fdbc09eab..a30ee95e4 100644 --- a/app/assets/javascripts/findSession.js +++ b/app/assets/javascripts/findSession.js @@ -4,32 +4,73 @@ context.JK.FindSessionScreen = function(app) { var logger = context.JK.logger; var sessionLatency; + var sessions = {}; + var selectors = { + TABLE_BODY: '#findSession-tableBody' + }; function loadSessions() { $.ajax({ type: "GET", url: "/api/sessions", - // TODO: Change to buildLatencyMap - success: renderSessions + success: startSessionLatencyChecks }); } - function renderSessions(response) { - $tb = $('#findSession-tableBody'); - $tb.empty(); - var rowTemplate = $('#template-findSession-row').html(); - $.each(response, function() { - var vals = { - id: this.id, - participants: this.participants.length, - description: this.description || "(No description)" - }; - var row = JK.fillTemplate(rowTemplate, vals); - $tb.append(row); + function startSessionLatencyChecks(response) { + sessionLatency.subscribe(latencyResponse); + $.each(response, function(index, session) { + sessions[session.id] = session; + sessionLatency.sessionPings(session); }); } + function latencyResponse(sessionId) { + renderSession(sessionId); + } + + /** + * Not used normally. Allows modular unit testing + * of the renderSession method without having to do + * as much heavy setup. + */ + function setSession(session) { sessions[session.id] = session; } + + /** + * Render a single session line into the table. + * It will be inserted at the appropriate place according to the + * sortScore in sessionLatency. + */ + function renderSession(sessionId) { + $tb = $(selectors.TABLE_BODY); + var rowTemplate = $('#template-findSession-row').html(); + var session = sessions[sessionId]; + var latencyInfo = sessionLatency.sessionInfo(sessionId); + var vals = { + id: session.id, + genres: session.genres.join (','), + sortScore: latencyInfo.sortScore, + participants: session.participants.length, + description: session.description || "(No description)" + }; + var row = JK.fillTemplate(rowTemplate, vals); + var insertedEarly = false; + $.each($('tr', $tb), function() { + $this = $(this); + var rowSortScore = parseInt($this.attr('data-sortScore'), 10); + if (vals.sortScore > rowSortScore) { + $this.before(row); + insertedEarly = true; + } + }); + if (!(insertedEarly)) { + return $tb.append(row); + } + } + function afterShow(data) { + $tb = $(selectors.TABLE_BODY); + $tb.empty(); loadSessions(); } @@ -47,20 +88,30 @@ $('#findSession-tableBody').on("click", '[action="delete"]', deleteSession); } - this.afterShow = afterShow; - - this.initialize = function() { - if ("jamClient" in context) { - sessionLatency = new context.JK.SessionLatency(jamClient); + /** + * Initialize, providing an instance of the SessionLatency class. + */ + function initialize(_sessionLatency) { + if (_sessionLatency) { + sessionLatency = _sessionLatency; } else { - logger.warn("No jamClient available. Session pings will not be performed."); + logger.warn("No sessionLatency provided."); } screenBindings = { 'afterShow': afterShow }; app.bindScreen('findSession', screenBindings); events(); - }; + } + + this.initialize = initialize; + this.afterShow = afterShow; + this.renderSession = renderSession; + + // Following exposed for easier testing. + this.setSession = setSession; + this.selectors = selectors; + }; })(window,jQuery); \ No newline at end of file diff --git a/app/assets/javascripts/sessionLatency.js b/app/assets/javascripts/sessionLatency.js index 0a7d1e739..79e4fa6eb 100644 --- a/app/assets/javascripts/sessionLatency.js +++ b/app/assets/javascripts/sessionLatency.js @@ -8,6 +8,7 @@ var sessionPingsOut = {}; var clientsToSessions = {}; var sessionLatency = {}; + var subscribers = []; function getSortScore(sessionId) { return sessionLatency[sessionId].sortScore; @@ -51,7 +52,7 @@ sessionPingsOut[session.id] = 0; } sessionPingsOut[session.id]++; - jamClient.testLatency(client, clientPingResponse); + jamClient.TestLatency(client, clientPingResponse); }); } @@ -59,6 +60,9 @@ var sessionId = clientsToSessions[response.id]; sessionPingsOut[sessionId]--; updateSessionLatency(sessionId, response); + $.each(subscribers, function() { + this(sessionId); + }); } function updateSessionLatency(sessionId, latencyResponse) { @@ -96,9 +100,14 @@ return sessionLatency[sessionId]; } + function subscribe(cb) { + subscribers.push(cb); + } + this.sessionPings = sessionPings; this.sessionInfo = sessionInfo; this.getSortScore = getSortScore; + this.subscribe = subscribe; return this; }; diff --git a/app/views/clients/_findSession.html.erb b/app/views/clients/_findSession.html.erb index e5696f6cb..d910ab47f 100644 --- a/app/views/clients/_findSession.html.erb +++ b/app/views/clients/_findSession.html.erb @@ -4,9 +4,13 @@

Home

- - - + + + + + + + @@ -14,9 +18,13 @@ diff --git a/app/views/clients/index.html.erb b/app/views/clients/index.html.erb index 3a86d3cd8..1c1952532 100644 --- a/app/views/clients/index.html.erb +++ b/app/views/clients/index.html.erb @@ -19,6 +19,11 @@ JK = JK || {}; + + // The following allows testing of native (jamClient) functionality + // with a fake. Uncomment when developing without the native client. + window.jamClient = new JK.FakeJamClient(); + <% if current_user %> JK.currentUserId = '<%= current_user.id %>'; <% else %> @@ -40,7 +45,11 @@ createSessionScreen.initialize(); var findSessionScreen = new JK.FindSessionScreen(jk); - findSessionScreen.initialize(); + var sessionLatency = null; + if ("jamClient" in window) { + sessionLatency = new JK.SessionLatency(window.jamClient); + } + findSessionScreen.initialize(sessionLatency); var sessionScreen = new JK.SessionScreen(jk); sessionScreen.initialize(); diff --git a/spec/javascripts/findSession.spec.js b/spec/javascripts/findSession.spec.js index 0f4b57c18..bb57370e6 100644 --- a/spec/javascripts/findSession.spec.js +++ b/spec/javascripts/findSession.spec.js @@ -3,11 +3,6 @@ describe("FindSession", function() { var fss; - var ajaxSpy; - - var selectors = { - sessions: '#findSession-tableBody' - }; var appFake = { clientId: '12345', @@ -16,34 +11,130 @@ ajaxError: function() { console.debug("ajaxError"); } }; + var jamClientFake = { + TestLatency: function(client, cb) { + cb({id: client.id, latency: 50}); + } + }; + beforeEach(function() { + fss = null; // Use the actual screen markup jasmine.getFixtures().fixturesPath = '/app/views/clients'; loadFixtures('_findSession.html.erb'); spyOn(appFake, 'notify'); - fss = new context.JK.FindSessionScreen(appFake); - fss.initialize(); }); - describe("loadSessions", function() { - beforeEach(function() { - spyOn($, "ajax"); - }); - it("should query ajax for sessions", function() { - fss.afterShow({}); - expect($.ajax).toHaveBeenCalled(); - }); - }); + var sessionLatencyReal; - describe("renderSessions", function() { + describe("RealSessionLatency", function() { beforeEach(function() { - spyOn($, "ajax").andCallFake(function(opts) { - opts.success(TestResponses.getSessions); + sessionLatencyReal = new JK.SessionLatency(jamClientFake); + spyOn(sessionLatencyReal, 'sessionPings').andCallThrough(); + fss = new context.JK.FindSessionScreen(appFake); + fss.initialize(sessionLatencyReal); + $(fss.selectors.TABLE_BODY).empty(); + }); + + describe("loadSessions", function() { + beforeEach(function() { + spyOn($, "ajax"); + }); + it("should query ajax for sessions", function() { + fss.afterShow({}); + expect($.ajax).toHaveBeenCalled(); }); }); - it("should output table rows for sessions", function() { - fss.afterShow({}); - expect($(selectors.sessions + ' tr').length).toEqual(1); + + describe("afterShow flow", function() { + beforeEach(function() { + spyOn($, "ajax").andCallFake(function(opts) { + opts.success(TestGetSessionResponses.oneOfEach); + }); + }); + + it("should output table rows for sessions", function() { + fss.afterShow({}); + expect($(fss.selectors.TABLE_BODY + ' tr').length).toEqual(5); + }); + + it("should call sessionPings", function() { + fss.afterShow({}); + expect(sessionLatencyReal.sessionPings).toHaveBeenCalled(); + }); + + }); + }); + + describe("FakeSessionLatency", function() { + + beforeEach(function() { + sessionInfoResponses = { + "1": {id:"1", sortScore: 3}, + "2": {id:"2", sortScore: 2} + }; + sessionLatencyFake = { + sessionInfo: null + }; + spyOn(sessionLatencyFake, 'sessionInfo').andCallFake(function(id) { + return sessionInfoResponses[id]; + }); + fss = new context.JK.FindSessionScreen(appFake); + fss.initialize(sessionLatencyFake); + $(fss.selectors.TABLE_BODY).empty(); + }); + + describe("renderSession", function() { + + describe("layout", function() { + var tbody; + + beforeEach(function() { + var session = TestGetSessionResponses.oneOfEach[0]; + fss.setSession(session); + fss.renderSession(session.id); + tbody = $(fss.selectors.TABLE_BODY); + }); + + it("single session should render", function() { + expect($('tr', tbody).length).toEqual(1); + }); + + it("Should render genre", function() { + expect($('tr td', tbody).first().text()).toEqual('classical'); + }); + + it("Should render description", function() { + expect($('tr td', tbody).first().next().text()).toEqual('Invited'); + }); + + it("Should render musician count", function() { + expect($('tr td', tbody).first().next().next().text()).toEqual('1'); + }); + + // TODO - test audience + // TODO - test latency + // TODO - test Listen + + it("Should render join link", function() { + expect($('tr td', tbody).last().text()).toEqual('Join'); + }); + + }); + + it("higher sortScore inserted before lower sortScore", function() { + var sessionLow = TestGetSessionResponses.oneOfEach[1]; + var sessionHigh = TestGetSessionResponses.oneOfEach[0]; + fss.setSession(sessionLow); + fss.setSession(sessionHigh); + fss.renderSession(sessionLow.id); + fss.renderSession(sessionHigh.id); + + var tbody = $(fss.selectors.TABLE_BODY); + expect($('tr', tbody).length).toEqual(2); + expect($('tr', tbody).first().attr('data-sortScore')).toEqual('3'); + expect($('tr', tbody).first().next().attr('data-sortScore')).toEqual('2'); + }); }); }); diff --git a/spec/javascripts/helpers/test_getSessionResponses.js b/spec/javascripts/helpers/test_getSessionResponses.js new file mode 100644 index 000000000..b526587be --- /dev/null +++ b/spec/javascripts/helpers/test_getSessionResponses.js @@ -0,0 +1,120 @@ +window.TestGetSessionResponses = { + oneOfEach: [ + + // Session 1 - you're invited to this. + { + "id": "1", + "description": "Invited", + "musician_access": true, + "genres" : [ "classical" ], + "participants": [ + { + "client_id": "0f8f7987-29a0-4e5d-a60c-6b23103e3533", + "ip_address":"1.1.1.1", + "user_id" : "02303020402042040", + "tracks" : [ + { + "id" : "xxxx", + "instrument_id" : "electric guitar", + "sound" : "mono" + } + ] + } + ], + "invitations" : [ + { + "id" : "3948797598275987", + "sender_id" : "02303020402042040" + + } + ] + }, + + // Session 2 - no invite, but friend #1 (good latency) + { + "id": "2", + "description": "Friends 1", + "musician_access": true, + "genres" : [ "blues" ], + "participants": [ + { + "client_id": "0f8f7987-29a0-4e5d-a60c-6b23103e3533", + "ip_address":"1.1.1.1", + "user_id" : "02303020402042040", + "tracks" : [ + { + "id" : "xxxx", + "instrument_id" : "electric guitar", + "sound" : "mono" + } + ] + } + ] + }, + + // Session 3 - no invite, but friend #2 (med latency) + { + "id": "3", + "description": "Friends 2", + "musician_access": true, + "genres" : [ "blues" ], + "participants": [ + { + "client_id": "0f8f7987-29a0-4e5d-a60c-6b23103e3533", + "ip_address":"1.1.1.1", + "user_id" : "02303020402042040", + "tracks" : [ + { + "id" : "xxxx", + "instrument_id" : "electric guitar", + "sound" : "mono" + } + ] + } + ] + }, + + // Session 4 - no invite, no friends 1 + { + "id": "4", + "description": "Anonymous 1", + "musician_access": true, + "genres" : [ "blues" ], + "participants": [ + { + "client_id": "0f8f7987-29a0-4e5d-a60c-6b23103e3533", + "ip_address":"1.1.1.1", + "tracks" : [ + { + "id" : "xxxx", + "instrument_id" : "electric guitar", + "sound" : "mono" + } + ] + } + ] + }, + + // Session 5 - no invite, no friends 2 + { + "id": "5", + "description": "Anonymous 2", + "musician_access": true, + "genres" : [ "blues" ], + "participants": [ + { + "client_id": "0f8f7987-29a0-4e5d-a60c-6b23103e3533", + "ip_address":"1.1.1.1", + "tracks" : [ + { + "id" : "xxxx", + "instrument_id" : "electric guitar", + "sound" : "mono" + } + ] + } + ] + } + + ] +}; \ No newline at end of file diff --git a/spec/javascripts/helpers/test_responses.js b/spec/javascripts/helpers/test_responses.js index a83371be9..ca9d890cb 100644 --- a/spec/javascripts/helpers/test_responses.js +++ b/spec/javascripts/helpers/test_responses.js @@ -1,34 +1,4 @@ window.TestResponses = { - getSessions: [ - { - "id": "1234", - "description": "Hello", - "musician_access": true, - "genres" : [ "classical" ], - "participants": [ - { - "client_id": "0f8f7987-29a0-4e5d-a60c-6b23103e3533", - "ip_address":"1.1.1.1", - "user_id" : "02303020402042040", // NOTE THIS WILL BE UNDEFINED (ABSENT) IF THIS CLIENT IS NOT YOUR FRIEND - "tracks" : [ - { - "id" : "xxxx", - "instrument_id" : "electric guitar", - "sound" : "mono" - } - ] - } - ], - "invitations" : [ - { - "id" : "3948797598275987", - "sender_id" : "02303020402042040" - - } - ] - } - ], - sessionPost: { "id": "1234", "description": "Hello", diff --git a/spec/javascripts/sessionLatency.spec.js b/spec/javascripts/sessionLatency.spec.js index 364281dc0..bfe3c86d3 100644 --- a/spec/javascripts/sessionLatency.spec.js +++ b/spec/javascripts/sessionLatency.spec.js @@ -5,7 +5,7 @@ var sessionLatency; // Instance of SessionLatency class for test fakeJamClient = { - testLatency: function() {} // Will be overridden by test cases + TestLatency: function() {} // Will be overridden by test cases }; var sessions = [ @@ -33,16 +33,16 @@ beforeEach(function() { callCount = 0; sessionLatency = new context.JK.SessionLatency(fakeJamClient); - spyOn(fakeJamClient, "testLatency").andCallFake(function(client, callback) { + spyOn(fakeJamClient, "TestLatency").andCallFake(function(client, callback) { callback(testLatencyResponses[client.id]); callCount++; }); }); describe("SessionPings", function() { - it("should call jamClient.testLatency and compute new average", function() { + it("should call jamClient.TestLatency and compute new average", function() { sessionLatency.sessionPings(sessions[0]); - expect(fakeJamClient.testLatency).toHaveBeenCalled(); + expect(fakeJamClient.TestLatency).toHaveBeenCalled(); var info = sessionLatency.sessionInfo(sessions[0].id); expect(info.averageLatency).toEqual(35); }); @@ -87,6 +87,21 @@ }); }); + describe("Register for Events", function() { + it("should register successfully", function() { + var cb = jasmine.createSpy(); + sessionLatency.subscribe(cb); + }); + it("should invoke callback on latency result", function() { + var cb = jasmine.createSpy("Latency Subscription Callback"); + sessionLatency.subscribe(cb); + $.each(sessions, function(index, session) { + sessionLatency.sessionPings(session); + }); + expect(cb).toHaveBeenCalled(); + }); + }); + });
MembersNameDeleteGenreDescriptionMusiciansAudienceLatencyListenJoin