From b2db5190c80a5da40b0a7fb25a62ddc602d9e68a Mon Sep 17 00:00:00 2001 From: Drew Regitsky Date: Mon, 7 Mar 2016 20:54:43 -0800 Subject: [PATCH] test(plugins): Add specs, refactor/fixes for open and link tracking Summary: Add specs to test the components of open tracking and link tracking. Notably does not test the overall functionality, which still needs specs. Test Plan: adds specs Reviewers: juan, evan, bengotow Reviewed By: evan, bengotow Differential Revision: https://phab.nylas.com/D2667 --- .../lib/link-tracking-after-send.es6 | 37 ++++++++ internal_packages/link-tracking/lib/main.es6 | 34 +------- .../spec/link-tracking-after-send-spec.es6 | 63 ++++++++++++++ .../link-tracking-composer-extension-spec.es6 | 85 +++++++++++++++++++ internal_packages/open-tracking/lib/main.es6 | 38 +-------- .../lib/open-tracking-after-send.es6 | 37 ++++++++ .../lib/open-tracking-composer-extension.es6 | 25 ++++-- .../open-tracking/lib/open-tracking-icon.jsx | 4 +- .../lib/open-tracking-message-status.jsx | 2 +- .../spec/open-tracking-after-send-spec.es6 | 67 +++++++++++++++ .../open-tracking-composer-extension-spec.es6 | 64 ++++++++++++++ .../spec/open-tracking-icon-spec.jsx | 80 +++++++++++++++++ .../open-tracking-message-status-spec.jsx | 54 ++++++++++++ 13 files changed, 512 insertions(+), 78 deletions(-) create mode 100644 internal_packages/link-tracking/lib/link-tracking-after-send.es6 create mode 100644 internal_packages/link-tracking/spec/link-tracking-after-send-spec.es6 create mode 100644 internal_packages/link-tracking/spec/link-tracking-composer-extension-spec.es6 create mode 100644 internal_packages/open-tracking/lib/open-tracking-after-send.es6 create mode 100644 internal_packages/open-tracking/spec/open-tracking-after-send-spec.es6 create mode 100644 internal_packages/open-tracking/spec/open-tracking-composer-extension-spec.es6 create mode 100644 internal_packages/open-tracking/spec/open-tracking-icon-spec.jsx create mode 100644 internal_packages/open-tracking/spec/open-tracking-message-status-spec.jsx diff --git a/internal_packages/link-tracking/lib/link-tracking-after-send.es6 b/internal_packages/link-tracking/lib/link-tracking-after-send.es6 new file mode 100644 index 000000000..57a1d2617 --- /dev/null +++ b/internal_packages/link-tracking/lib/link-tracking-after-send.es6 @@ -0,0 +1,37 @@ +import request from 'request'; +import {Actions} from 'nylas-exports'; +import {PLUGIN_ID, PLUGIN_URL} from './link-tracking-constants' + +export default class LinkTrackingAfterSend{ + static post = Promise.promisify(request.post, {multiArgs: true}); + + static afterDraftSend({message}) { + // only run this handler in the main window + if (!NylasEnv.isMainWindow()) return; + + // grab message metadata, if any + const metadata = message.metadataForPluginId(PLUGIN_ID); + if (metadata && metadata.uid) { + // get the uid from the metadata, if present + const uid = metadata.uid; + + // post the uid and message id pair to the plugin server + const data = {uid: uid, message_id: message.id}; + const serverUrl = `${PLUGIN_URL}/plugins/register-message`; + + LinkTrackingAfterSend.post({ + url: serverUrl, + body: JSON.stringify(data), + }).then(([response, responseBody]) => { + if (response.statusCode !== 200) { + throw new Error(`Server error ${response.statusCode} at ${serverUrl}: ${responseBody}`); + } + }).catch(error => { + NylasEnv.showErrorDialog(`There was a problem saving your link tracking settings. This message will not have link tracking. ${error.message}`); + // clear metadata - link tracking won't work for this message. + Actions.setMetadata(message, PLUGIN_ID, null); + Promise.reject(error); + }); + } + } +} \ No newline at end of file diff --git a/internal_packages/link-tracking/lib/main.es6 b/internal_packages/link-tracking/lib/main.es6 index e37a77888..c0a12e728 100644 --- a/internal_packages/link-tracking/lib/main.es6 +++ b/internal_packages/link-tracking/lib/main.es6 @@ -3,44 +3,14 @@ import {ComponentRegistry, ExtensionRegistry, Actions} from 'nylas-exports'; import LinkTrackingButton from './link-tracking-button'; import LinkTrackingComposerExtension from './link-tracking-composer-extension'; import LinkTrackingMessageExtension from './link-tracking-message-extension'; -import {PLUGIN_ID, PLUGIN_URL} from './link-tracking-constants'; +import LinkTrackingAfterSend from './link-tracking-after-send'; -const post = Promise.promisify(request.post, {multiArgs: true}); - - -function afterDraftSend({message}) { - // only run this handler in the main window - if (!NylasEnv.isMainWindow()) return; - - // grab message metadata, if any - const metadata = message.metadataForPluginId(PLUGIN_ID); - if (metadata) { - // get the uid from the metadata, if present - const uid = metadata.uid; - - // post the uid and message id pair to the plugin server - const data = {uid: uid, message_id: message.id}; - const serverUrl = `${PLUGIN_URL}/plugins/register-message`; - - post({ - url: serverUrl, - body: JSON.stringify(data), - }).then(([response, responseBody]) => { - if (response.statusCode !== 200) { - throw new Error(`Server error ${response.statusCode} at ${serverUrl}: ${responseBody}`); - } - }).catch(error => { - NylasEnv.showErrorDialog(`There was a problem saving your link tracking settings. This message will not have link tracking. ${error.message}`); - Promise.reject(error); - }); - } -} export function activate() { ComponentRegistry.register(LinkTrackingButton, {role: 'Composer:ActionButton'}); ExtensionRegistry.Composer.register(LinkTrackingComposerExtension); ExtensionRegistry.MessageView.register(LinkTrackingMessageExtension); - this._unlistenSendDraftSuccess = Actions.sendDraftSuccess.listen(afterDraftSend); + this._unlistenSendDraftSuccess = Actions.sendDraftSuccess.listen(LinkTrackingAfterSend.afterDraftSend); } export function serialize() {} diff --git a/internal_packages/link-tracking/spec/link-tracking-after-send-spec.es6 b/internal_packages/link-tracking/spec/link-tracking-after-send-spec.es6 new file mode 100644 index 000000000..464f6f4f8 --- /dev/null +++ b/internal_packages/link-tracking/spec/link-tracking-after-send-spec.es6 @@ -0,0 +1,63 @@ +import {Message} from 'nylas-exports' +import {PLUGIN_ID, PLUGIN_URL} from '../lib/link-tracking-constants'; +import LinkTrackingAfterSend from '../lib/link-tracking-after-send' + +describe("Link tracking afterDraftSend callback", () => { + beforeEach(() => { + this.message = new Message(); + this.postResponse = 200; + spyOn(LinkTrackingAfterSend, "post").andCallFake(() => Promise.resolve([{statusCode: this.postResponse}, ""])); + spyOn(NylasEnv, "isMainWindow").andReturn(true); + }); + + it("takes no action when the message has no metadata", () => { + LinkTrackingAfterSend.afterDraftSend({message: this.message}); + expect(LinkTrackingAfterSend.post).not.toHaveBeenCalled(); + }); + + it("takes no action when the message has malformed metadata", () => { + this.message.applyPluginMetadata(PLUGIN_ID, {gar: "bage"}); + LinkTrackingAfterSend.afterDraftSend({message: this.message}); + expect(LinkTrackingAfterSend.post).not.toHaveBeenCalled(); + }); + + describe("When metadata is present", () => { + beforeEach(() => { + this.metadata = {uid: "TEST_UID"}; + this.message.applyPluginMetadata(PLUGIN_ID, this.metadata); + }); + + it("posts UID => message ID to the server", () => { + // Spy on the POST request, then call the afterDraftSend function + LinkTrackingAfterSend.afterDraftSend({message: this.message}); + + expect(LinkTrackingAfterSend.post).toHaveBeenCalled(); + const {url, body} = LinkTrackingAfterSend.post.mostRecentCall.args[0]; + const {uid, message_id} = JSON.parse(body); + + expect(url).toEqual(`${PLUGIN_URL}/plugins/register-message`); + expect(uid).toEqual(this.metadata.uid); + expect(message_id).toEqual(this.message.id); + }); + + + it("shows an error dialog if the request fails", () => { + // Spy on the POST request and dialog function + this.postResponse = 400; + spyOn(NylasEnv, "showErrorDialog"); + spyOn(NylasEnv, "reportError"); + + LinkTrackingAfterSend.afterDraftSend({message: this.message}); + + expect(LinkTrackingAfterSend.post).toHaveBeenCalled(); + + waitsFor(() => { + return NylasEnv.reportError.callCount > 0; + }); + runs(() => { + expect(NylasEnv.showErrorDialog).toHaveBeenCalled(); + expect(NylasEnv.reportError).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/internal_packages/link-tracking/spec/link-tracking-composer-extension-spec.es6 b/internal_packages/link-tracking/spec/link-tracking-composer-extension-spec.es6 new file mode 100644 index 000000000..59ced484f --- /dev/null +++ b/internal_packages/link-tracking/spec/link-tracking-composer-extension-spec.es6 @@ -0,0 +1,85 @@ +import LinkTrackingComposerExtension from '../lib/link-tracking-composer-extension' +import {PLUGIN_ID, PLUGIN_URL} from '../lib/link-tracking-constants'; +import {Message, QuotedHTMLTransformer} from 'nylas-exports'; + +const testContent = `TEST_BODY
+test +asdad +adsasd +stillhere +
+http://www.stillhere.com`; + +const replacedContent = (accountId, messageUid) => `TEST_BODY
+test +asdad +adsasd +stillhere +
+http://www.stillhere.com`; + +const quote = `
On Feb 25 2016, at 3:38 pm, Drew <drew@nylas.com> wrote:
twst
`; +const testBody = `${testContent}${quote}`; +const replacedBody = (accountId, messageUid, unquoted) => `${replacedContent(accountId, messageUid)}${unquoted ? "" : quote}`; + +describe("Open tracking composer extension", () => { + + // Set up a draft, session that returns the draft, and metadata + beforeEach(()=>{ + this.draft = new Message({accountId: "test"}); + this.draft.body = testBody; + this.session = { + draft: () => this.draft, + changes: jasmine.createSpyObj('changes', ['add', 'commit']) + }; + }); + + it("takes no action if there is no metadata", ()=>{ + LinkTrackingComposerExtension.finalizeSessionBeforeSending({session: this.session}); + expect(this.session.changes.add).not.toHaveBeenCalled(); + expect(this.session.changes.commit).not.toHaveBeenCalled(); + }); + + describe("With properly formatted metadata and correct params", () => { + // Set metadata on the draft and call finalizeSessionBeforeSending + beforeEach(()=>{ + this.metadata = {tracked: true}; + this.draft.applyPluginMetadata(PLUGIN_ID, this.metadata); + LinkTrackingComposerExtension.finalizeSessionBeforeSending({session: this.session}); + }); + + it("adds (but does not commit) the changes to the session", ()=>{ + expect(this.session.changes.add).toHaveBeenCalled(); + expect(this.session.changes.add.mostRecentCall.args[0].body).toBeDefined(); + expect(this.session.changes.commit).not.toHaveBeenCalled(); + }); + + describe("On the unquoted body", () => { + beforeEach(()=>{ + this.body = this.session.changes.add.mostRecentCall.args[0].body; + this.unquoted = QuotedHTMLTransformer.removeQuotedHTML(this.body); + + waitsFor(()=>this.metadata.uid) + }); + + it("sets a uid and list of links on the metadata", ()=>{ + runs(() => { + expect(this.metadata.uid).not.toBeUndefined(); + expect(this.metadata.links).not.toBeUndefined(); + expect(this.metadata.links.length).toEqual(2); + + for (const link of this.metadata.links) { + expect(link.click_count).toEqual(0); + } + }) + }); + + it("replaces all the valid href URLs with redirects", ()=>{ + runs(() => { + expect(this.unquoted).toContain(replacedBody(this.draft.accountId, this.metadata.uid, true)); + expect(this.body).toContain(replacedBody(this.draft.accountId, this.metadata.uid, false)); + }) + }); + }) + }); +}); diff --git a/internal_packages/open-tracking/lib/main.es6 b/internal_packages/open-tracking/lib/main.es6 index ee286a930..db951c4d2 100644 --- a/internal_packages/open-tracking/lib/main.es6 +++ b/internal_packages/open-tracking/lib/main.es6 @@ -1,49 +1,17 @@ -import request from 'request'; + import {ComponentRegistry, ExtensionRegistry, Actions} from 'nylas-exports'; import OpenTrackingButton from './open-tracking-button'; import OpenTrackingIcon from './open-tracking-icon'; import OpenTrackingMessageStatus from './open-tracking-message-status'; +import OpenTrackingAfterSend from './open-tracking-after-send'; import OpenTrackingComposerExtension from './open-tracking-composer-extension'; -import {PLUGIN_ID, PLUGIN_URL} from './open-tracking-constants' - -const post = Promise.promisify(request.post, {multiArgs: true}); - - -function afterDraftSend({message}) { - // only run this handler in the main window - if (!NylasEnv.isMainWindow()) return; - - // grab message metadata, if any - const metadata = message.metadataForPluginId(PLUGIN_ID); - - // get the uid from the metadata, if present - if (metadata) { - const uid = metadata.uid; - - // post the uid and message id pair to the plugin server - const data = {uid: uid, message_id: message.id, thread_id: 1}; - const serverUrl = `${PLUGIN_URL}/plugins/register-message`; - - post({ - url: serverUrl, - body: JSON.stringify(data), - }).then(([response, responseBody]) => { - if (response.statusCode !== 200) { - throw new Error(`Server error ${response.statusCode} at ${serverUrl}: ${responseBody}`); - } - }).catch(error => { - NylasEnv.showErrorDialog(`There was a problem saving your read receipt settings. This message will not have a read receipt. ${error.message}`); - Promise.reject(error); - }); - } -} export function activate() { ComponentRegistry.register(OpenTrackingButton, {role: 'Composer:ActionButton'}); ComponentRegistry.register(OpenTrackingIcon, {role: 'ThreadListIcon'}); ComponentRegistry.register(OpenTrackingMessageStatus, {role: 'MessageHeaderStatus'}); ExtensionRegistry.Composer.register(OpenTrackingComposerExtension); - this._unlistenSendDraftSuccess = Actions.sendDraftSuccess.listen(afterDraftSend); + this._unlistenSendDraftSuccess = Actions.sendDraftSuccess.listen(OpenTrackingAfterSend.afterDraftSend); } export function serialize() {} diff --git a/internal_packages/open-tracking/lib/open-tracking-after-send.es6 b/internal_packages/open-tracking/lib/open-tracking-after-send.es6 new file mode 100644 index 000000000..7cdfafc4a --- /dev/null +++ b/internal_packages/open-tracking/lib/open-tracking-after-send.es6 @@ -0,0 +1,37 @@ +import request from 'request'; +import {Actions} from 'nylas-exports'; +import {PLUGIN_ID, PLUGIN_URL} from './open-tracking-constants' + +export default class OpenTrackingAfterSend { + static post = Promise.promisify(request.post, {multiArgs: true}); + + static afterDraftSend({message}) { + // only run this handler in the main window + if (!NylasEnv.isMainWindow()) return; + + // grab message metadata, if any + const metadata = message.metadataForPluginId(PLUGIN_ID); + if (metadata && metadata.uid) { + // get the uid from the metadata, if present + const uid = metadata.uid; + + // post the uid and message id pair to the plugin server + const data = {uid: uid, message_id: message.id}; + const serverUrl = `${PLUGIN_URL}/plugins/register-message`; + + OpenTrackingAfterSend.post({ + url: serverUrl, + body: JSON.stringify(data), + }).then(([response, responseBody]) => { + if (response.statusCode !== 200) { + throw new Error(`Server error ${response.statusCode} at ${serverUrl}: ${responseBody}`); + } + }).catch(error => { + NylasEnv.showErrorDialog(`There was a problem saving your read receipt settings. This message will not have a read receipt. ${error.message}`); + // clear metadata - open tracking won't work for this message. + Actions.setMetadata(message, PLUGIN_ID, null); + Promise.reject(error); + }); + } + } +} \ No newline at end of file diff --git a/internal_packages/open-tracking/lib/open-tracking-composer-extension.es6 b/internal_packages/open-tracking/lib/open-tracking-composer-extension.es6 index b1d92142f..369253c6f 100644 --- a/internal_packages/open-tracking/lib/open-tracking-composer-extension.es6 +++ b/internal_packages/open-tracking/lib/open-tracking-composer-extension.es6 @@ -15,15 +15,22 @@ export default class OpenTrackingComposerExtension extends ComposerExtension { // grab message metadata, if any const metadata = draft.metadataForPluginId(PLUGIN_ID); - if (metadata) { - // insert a tracking pixel into the message - const serverUrl = `${PLUGIN_URL}/open/${draft.accountId}/${metadata.uid}`; - const img = ``; - const draftBody = new DraftBody(draft); - draftBody.unquoted = draftBody.unquoted + "
" + img; - - // save the draft - session.changes.add({body: draftBody.body}); + if (!metadata) { + return; } + + if (!metadata.uid) { + NylasEnv.reportError(new Error("Open tracking composer extension could not find 'uid' in metadata!")); + return; + } + + // insert a tracking pixel into the message + const serverUrl = `${PLUGIN_URL}/open/${draft.accountId}/${metadata.uid}`; + const img = ``; + const draftBody = new DraftBody(draft); + draftBody.unquoted = draftBody.unquoted + "
" + img; + + // save the draft + session.changes.add({body: draftBody.body}); } } diff --git a/internal_packages/open-tracking/lib/open-tracking-icon.jsx b/internal_packages/open-tracking/lib/open-tracking-icon.jsx index d16f6e265..f7e8c8a0b 100644 --- a/internal_packages/open-tracking/lib/open-tracking-icon.jsx +++ b/internal_packages/open-tracking/lib/open-tracking-icon.jsx @@ -22,7 +22,9 @@ export default class OpenTrackingIcon extends React.Component { _getStateFromThread(thread) { const messages = thread.metadata; if ((messages || []).length === 0) { return {opened: false, hasMetadata: false} } - const metadataObjs = messages.map(msg => msg.metadataForPluginId(PLUGIN_ID)).filter(meta => meta); + const metadataObjs = messages + .map(msg => msg.metadataForPluginId(PLUGIN_ID)) + .filter(meta => meta && meta.open_count != null); return { hasMetadata: metadataObjs.length > 0, opened: metadataObjs.length > 0 && metadataObjs.every(m => m.open_count > 0), diff --git a/internal_packages/open-tracking/lib/open-tracking-message-status.jsx b/internal_packages/open-tracking/lib/open-tracking-message-status.jsx index 4b38bb59a..fdc53b474 100644 --- a/internal_packages/open-tracking/lib/open-tracking-message-status.jsx +++ b/internal_packages/open-tracking/lib/open-tracking-message-status.jsx @@ -20,7 +20,7 @@ export default class OpenTrackingMessageStatus extends React.Component { _getStateFromMessage(message) { const metadata = message.metadataForPluginId(PLUGIN_ID); - if (!metadata) { + if (!metadata || metadata.open_count == null) { return {hasMetadata: false, opened: false} } return { diff --git a/internal_packages/open-tracking/spec/open-tracking-after-send-spec.es6 b/internal_packages/open-tracking/spec/open-tracking-after-send-spec.es6 new file mode 100644 index 000000000..abc03d79d --- /dev/null +++ b/internal_packages/open-tracking/spec/open-tracking-after-send-spec.es6 @@ -0,0 +1,67 @@ +import {Message} from 'nylas-exports' +import {PLUGIN_ID, PLUGIN_URL} from '../lib/open-tracking-constants'; +import OpenTrackingAfterSend from '../lib/open-tracking-after-send' + +function fakeResponse(statusCode, body) { + return [{statusCode}, body]; +} + +describe("Open tracking afterDraftSend callback", () => { + beforeEach(() => { + this.message = new Message(); + this.postResponse = 200; + spyOn(OpenTrackingAfterSend, "post").andCallFake(() => Promise.resolve(fakeResponse(this.postResponse, ""))); + spyOn(NylasEnv, "isMainWindow").andReturn(true); + }); + + it("takes no action when the message has no metadata", () => { + OpenTrackingAfterSend.afterDraftSend({message: this.message}); + expect(OpenTrackingAfterSend.post).not.toHaveBeenCalled(); + }); + + it("takes no action when the message has malformed metadata", () => { + this.message.applyPluginMetadata(PLUGIN_ID, {gar: "bage"}); + OpenTrackingAfterSend.afterDraftSend({message: this.message}); + expect(OpenTrackingAfterSend.post).not.toHaveBeenCalled(); + }); + + describe("When metadata is present", () => { + beforeEach(() => { + this.metadata = {uid: "TEST_UID"}; + this.message.applyPluginMetadata(PLUGIN_ID, this.metadata); + }); + + it("posts UID => message ID to the server", () => { + // Spy on the POST request, then call the afterDraftSend function + OpenTrackingAfterSend.afterDraftSend({message: this.message}); + + expect(OpenTrackingAfterSend.post).toHaveBeenCalled(); + const {url, body} = OpenTrackingAfterSend.post.mostRecentCall.args[0]; + const {uid, message_id} = JSON.parse(body); + + expect(url).toEqual(`${PLUGIN_URL}/plugins/register-message`); + expect(uid).toEqual(this.metadata.uid); + expect(message_id).toEqual(this.message.id); + }); + + + it("shows an error dialog if the request fails", () => { + // Spy on the POST request and dialog function + this.postResponse = 400; + spyOn(NylasEnv, "showErrorDialog"); + spyOn(NylasEnv, "reportError"); + + OpenTrackingAfterSend.afterDraftSend({message: this.message}); + + expect(OpenTrackingAfterSend.post).toHaveBeenCalled(); + + waitsFor(() => { + return NylasEnv.reportError.callCount > 0; + }); + runs(() => { + expect(NylasEnv.showErrorDialog).toHaveBeenCalled(); + expect(NylasEnv.reportError).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/internal_packages/open-tracking/spec/open-tracking-composer-extension-spec.es6 b/internal_packages/open-tracking/spec/open-tracking-composer-extension-spec.es6 new file mode 100644 index 000000000..ca39cdfe3 --- /dev/null +++ b/internal_packages/open-tracking/spec/open-tracking-composer-extension-spec.es6 @@ -0,0 +1,64 @@ +import OpenTrackingComposerExtension from '../lib/open-tracking-composer-extension' +import {PLUGIN_ID, PLUGIN_URL} from '../lib/open-tracking-constants'; +import {Message, QuotedHTMLTransformer} from 'nylas-exports'; + +const quote = `
On Feb 25 2016, at 3:38 pm, Drew <drew@nylas.com> wrote:
twst
`; + +describe("Open tracking composer extension", () => { + + // Set up a draft, session that returns the draft, and metadata + beforeEach(()=>{ + this.draft = new Message(); + this.draft.body = `TEST_BODY ${quote}`; + this.session = { + draft: () => this.draft, + changes: jasmine.createSpyObj('changes', ['add', 'commit']) + }; + }); + + it("takes no action if there is no metadata", ()=>{ + OpenTrackingComposerExtension.finalizeSessionBeforeSending({session: this.session}); + expect(this.session.changes.add.calls.length).toEqual(0); + expect(this.session.changes.commit.calls.length).toEqual(0); + }); + + describe("With properly formatted metadata and correct params", () => { + // Set metadata on the draft and call finalizeSessionBeforeSending + beforeEach(()=>{ + this.metadata = {uid: "TEST_UID"}; + this.draft.applyPluginMetadata(PLUGIN_ID, this.metadata); + OpenTrackingComposerExtension.finalizeSessionBeforeSending({session: this.session}); + }); + + it("adds (but does not commit) the changes to the session", ()=>{ + expect(this.session.changes.add).toHaveBeenCalled(); + expect(this.session.changes.add.mostRecentCall.args[0].body).toBeDefined(); + expect(this.session.changes.add.mostRecentCall.args[0].body).toContain("TEST_BODY"); + expect(this.session.changes.commit).not.toHaveBeenCalled(); + }); + + describe("On the unquoted body", () => { + beforeEach(()=>{ + const body = this.session.changes.add.mostRecentCall.args[0].body; + this.unquoted = QuotedHTMLTransformer.removeQuotedHTML(body); + }); + + it("appends an image to the body", ()=>{ + expect(this.unquoted).toMatch(//); + }); + + it("has the right server URL", ()=>{ + const img = this.unquoted.match(//)[0]; + expect(img).toContain(`${PLUGIN_URL}/open/${this.draft.accountId}/${this.metadata.uid}`); + }); + }) + }); + + it("reports an error if the metadata is missing required fields", ()=>{ + this.draft.applyPluginMetadata(PLUGIN_ID, {}); + spyOn(NylasEnv, "reportError"); + OpenTrackingComposerExtension.finalizeSessionBeforeSending({session: this.session}); + expect(NylasEnv.reportError).toHaveBeenCalled() + }); + +}); diff --git a/internal_packages/open-tracking/spec/open-tracking-icon-spec.jsx b/internal_packages/open-tracking/spec/open-tracking-icon-spec.jsx new file mode 100644 index 000000000..69eb59b0c --- /dev/null +++ b/internal_packages/open-tracking/spec/open-tracking-icon-spec.jsx @@ -0,0 +1,80 @@ +import React, {addons} from 'react/addons'; +import {Message} from 'nylas-exports' +import {renderIntoDocument} from '../../../spec/nylas-test-utils' +import OpenTrackingIcon from '../lib/open-tracking-icon' +import {PLUGIN_ID} from '../lib/open-tracking-constants' + + +const {findDOMNode} = React; +const {TestUtils: {findRenderedDOMComponentWithClass}} = addons; + +function makeIcon(thread, props = {}) { + return renderIntoDocument(); +} + +function find(component, className) { + return findDOMNode(findRenderedDOMComponentWithClass(component, className)) +} + +function addOpenMetadata(obj, openCount) { + obj.applyPluginMetadata(PLUGIN_ID, {open_count: openCount}); +} + +describe("Open tracking icon", () => { + beforeEach(() => { + this.thread = {metadata:[]}; + }); + + + it("shows no icon if the thread has no messages", () => { + const icon = find(makeIcon(this.thread), "open-tracking-icon"); + expect(icon.children.length).toEqual(0); + }); + + it("shows no icon if the thread messages have no metadata", () => { + this.thread.metadata.push(new Message()); + this.thread.metadata.push(new Message()); + const icon = find(makeIcon(this.thread), "open-tracking-icon"); + expect(icon.children.length).toEqual(0); + }); + + describe("With messages and metadata", () => { + beforeEach(() => { + this.messages = [new Message(), new Message(), new Message()]; + this.thread.metadata.push(...this.messages); + }); + + it("shows no icon if metadata is malformed", () => { + this.messages[0].applyPluginMetadata(PLUGIN_ID, {gar: "bage"}); + const icon = find(makeIcon(this.thread), "open-tracking-icon"); + expect(icon.children.length).toEqual(0); + }); + + it("shows an unopened icon if one message has metadata and is unopened", () => { + addOpenMetadata(this.messages[1], 0); + const icon = find(makeIcon(this.thread), "open-tracking-icon"); + expect(icon.children.length).toEqual(1); + expect(icon.querySelector("img.unopened")).not.toBeNull(); + expect(icon.querySelector("img.opened")).toBeNull(); + }); + + it("shows an unopened icon if only some messages are unopened", () => { + addOpenMetadata(this.messages[0], 0); + addOpenMetadata(this.messages[1], 1); + const icon = find(makeIcon(this.thread), "open-tracking-icon"); + expect(icon.children.length).toEqual(1); + expect(icon.querySelector("img.unopened")).not.toBeNull(); + expect(icon.querySelector("img.opened")).toBeNull(); + + }); + + it("shows an opened icon if all messages with metadata are opened", () => { + addOpenMetadata(this.messages[1], 1); + addOpenMetadata(this.messages[2], 1); + const icon = find(makeIcon(this.thread), "open-tracking-icon"); + expect(icon.children.length).toEqual(1); + expect(icon.querySelector("img.unopened")).toBeNull(); + expect(icon.querySelector("img.opened")).not.toBeNull(); + }); + }); +}); \ No newline at end of file diff --git a/internal_packages/open-tracking/spec/open-tracking-message-status-spec.jsx b/internal_packages/open-tracking/spec/open-tracking-message-status-spec.jsx new file mode 100644 index 000000000..6c941322f --- /dev/null +++ b/internal_packages/open-tracking/spec/open-tracking-message-status-spec.jsx @@ -0,0 +1,54 @@ +import React, {addons} from 'react/addons'; +import {Message} from 'nylas-exports' +import {renderIntoDocument} from '../../../spec/nylas-test-utils' +import OpenTrackingMessageStatus from '../lib/open-tracking-message-status' +import {PLUGIN_ID} from '../lib/open-tracking-constants' + + +const {findDOMNode} = React; +const {TestUtils: {findRenderedDOMComponentWithClass}} = addons; + +function makeIcon(message, props = {}) { + return renderIntoDocument(
); +} + +function find(component, className) { + return findDOMNode(findRenderedDOMComponentWithClass(component, className)) +} + +function addOpenMetadata(obj, openCount) { + obj.applyPluginMetadata(PLUGIN_ID, {open_count: openCount}); +} + +describe("Open tracking message status", () => { + beforeEach(() => { + this.message = new Message(); + }); + + + it("shows nothing if the message has no metadata", () => { + const icon = find(makeIcon(this.message), "temp"); + expect(icon.querySelector(".read-receipt-message-status")).toBeNull(); + }); + + + it("shows nothing if metadata is malformed", () => { + this.message.applyPluginMetadata(PLUGIN_ID, {gar: "bage"}); + const icon = find(makeIcon(this.message), "temp"); + expect(icon.querySelector(".read-receipt-message-status")).toBeNull(); + }); + + it("shows an unopened icon if the message has metadata and is unopened", () => { + addOpenMetadata(this.message, 0); + const icon = find(makeIcon(this.message), "read-receipt-message-status"); + expect(icon.querySelector("img.unopened")).not.toBeNull(); + expect(icon.querySelector("img.opened")).toBeNull(); + }); + + it("shows an opened icon if the message has metadata and is opened", () => { + addOpenMetadata(this.message, 1); + const icon = find(makeIcon(this.message), "read-receipt-message-status"); + expect(icon.querySelector("img.unopened")).toBeNull(); + expect(icon.querySelector("img.opened")).not.toBeNull(); + }); +}); \ No newline at end of file