mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-10-06 19:26:55 +08:00
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
This commit is contained in:
parent
f1d3959591
commit
b2db5190c8
13 changed files with 512 additions and 78 deletions
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,44 +3,14 @@ import {ComponentRegistry, ExtensionRegistry, Actions} from 'nylas-exports';
|
||||||
import LinkTrackingButton from './link-tracking-button';
|
import LinkTrackingButton from './link-tracking-button';
|
||||||
import LinkTrackingComposerExtension from './link-tracking-composer-extension';
|
import LinkTrackingComposerExtension from './link-tracking-composer-extension';
|
||||||
import LinkTrackingMessageExtension from './link-tracking-message-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() {
|
export function activate() {
|
||||||
ComponentRegistry.register(LinkTrackingButton, {role: 'Composer:ActionButton'});
|
ComponentRegistry.register(LinkTrackingButton, {role: 'Composer:ActionButton'});
|
||||||
ExtensionRegistry.Composer.register(LinkTrackingComposerExtension);
|
ExtensionRegistry.Composer.register(LinkTrackingComposerExtension);
|
||||||
ExtensionRegistry.MessageView.register(LinkTrackingMessageExtension);
|
ExtensionRegistry.MessageView.register(LinkTrackingMessageExtension);
|
||||||
this._unlistenSendDraftSuccess = Actions.sendDraftSuccess.listen(afterDraftSend);
|
this._unlistenSendDraftSuccess = Actions.sendDraftSuccess.listen(LinkTrackingAfterSend.afterDraftSend);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function serialize() {}
|
export function serialize() {}
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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<br>
|
||||||
|
<a href="www.replaced.com">test</a>
|
||||||
|
<a style="color: #aaa" href="http://replaced">asdad</a>
|
||||||
|
<a hre="www.stillhere.com">adsasd</a>
|
||||||
|
<a stillhere="">stillhere</a>
|
||||||
|
<div href="stillhere"></div>
|
||||||
|
http://www.stillhere.com`;
|
||||||
|
|
||||||
|
const replacedContent = (accountId, messageUid) => `TEST_BODY<br>
|
||||||
|
<a href="${PLUGIN_URL}/link/${accountId}/${messageUid}/0?redirect=www.replaced.com">test</a>
|
||||||
|
<a style="color: #aaa" href="${PLUGIN_URL}/link/${accountId}/${messageUid}/1?redirect=http%3A%2F%2Freplaced">asdad</a>
|
||||||
|
<a hre="www.stillhere.com">adsasd</a>
|
||||||
|
<a stillhere="">stillhere</a>
|
||||||
|
<div href="stillhere"></div>
|
||||||
|
http://www.stillhere.com`;
|
||||||
|
|
||||||
|
const quote = `<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;"> On Feb 25 2016, at 3:38 pm, Drew <drew@nylas.com> wrote: <br> twst </blockquote>`;
|
||||||
|
const testBody = `<body>${testContent}${quote}</body>`;
|
||||||
|
const replacedBody = (accountId, messageUid, unquoted) => `<body>${replacedContent(accountId, messageUid)}${unquoted ? "" : quote}</body>`;
|
||||||
|
|
||||||
|
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));
|
||||||
|
})
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,49 +1,17 @@
|
||||||
import request from 'request';
|
|
||||||
import {ComponentRegistry, ExtensionRegistry, Actions} from 'nylas-exports';
|
import {ComponentRegistry, ExtensionRegistry, Actions} from 'nylas-exports';
|
||||||
import OpenTrackingButton from './open-tracking-button';
|
import OpenTrackingButton from './open-tracking-button';
|
||||||
import OpenTrackingIcon from './open-tracking-icon';
|
import OpenTrackingIcon from './open-tracking-icon';
|
||||||
import OpenTrackingMessageStatus from './open-tracking-message-status';
|
import OpenTrackingMessageStatus from './open-tracking-message-status';
|
||||||
|
import OpenTrackingAfterSend from './open-tracking-after-send';
|
||||||
import OpenTrackingComposerExtension from './open-tracking-composer-extension';
|
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() {
|
export function activate() {
|
||||||
ComponentRegistry.register(OpenTrackingButton, {role: 'Composer:ActionButton'});
|
ComponentRegistry.register(OpenTrackingButton, {role: 'Composer:ActionButton'});
|
||||||
ComponentRegistry.register(OpenTrackingIcon, {role: 'ThreadListIcon'});
|
ComponentRegistry.register(OpenTrackingIcon, {role: 'ThreadListIcon'});
|
||||||
ComponentRegistry.register(OpenTrackingMessageStatus, {role: 'MessageHeaderStatus'});
|
ComponentRegistry.register(OpenTrackingMessageStatus, {role: 'MessageHeaderStatus'});
|
||||||
ExtensionRegistry.Composer.register(OpenTrackingComposerExtension);
|
ExtensionRegistry.Composer.register(OpenTrackingComposerExtension);
|
||||||
this._unlistenSendDraftSuccess = Actions.sendDraftSuccess.listen(afterDraftSend);
|
this._unlistenSendDraftSuccess = Actions.sendDraftSuccess.listen(OpenTrackingAfterSend.afterDraftSend);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function serialize() {}
|
export function serialize() {}
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,15 @@ export default class OpenTrackingComposerExtension extends ComposerExtension {
|
||||||
|
|
||||||
// grab message metadata, if any
|
// grab message metadata, if any
|
||||||
const metadata = draft.metadataForPluginId(PLUGIN_ID);
|
const metadata = draft.metadataForPluginId(PLUGIN_ID);
|
||||||
if (metadata) {
|
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 <img> into the message
|
// insert a tracking pixel <img> into the message
|
||||||
const serverUrl = `${PLUGIN_URL}/open/${draft.accountId}/${metadata.uid}`;
|
const serverUrl = `${PLUGIN_URL}/open/${draft.accountId}/${metadata.uid}`;
|
||||||
const img = `<img width="0" height="0" style="border:0; width:0; height:0;" src="${serverUrl}">`;
|
const img = `<img width="0" height="0" style="border:0; width:0; height:0;" src="${serverUrl}">`;
|
||||||
|
@ -26,4 +34,3 @@ export default class OpenTrackingComposerExtension extends ComposerExtension {
|
||||||
session.changes.add({body: draftBody.body});
|
session.changes.add({body: draftBody.body});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -22,7 +22,9 @@ export default class OpenTrackingIcon extends React.Component {
|
||||||
_getStateFromThread(thread) {
|
_getStateFromThread(thread) {
|
||||||
const messages = thread.metadata;
|
const messages = thread.metadata;
|
||||||
if ((messages || []).length === 0) { return {opened: false, hasMetadata: false} }
|
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 {
|
return {
|
||||||
hasMetadata: metadataObjs.length > 0,
|
hasMetadata: metadataObjs.length > 0,
|
||||||
opened: metadataObjs.length > 0 && metadataObjs.every(m => m.open_count > 0),
|
opened: metadataObjs.length > 0 && metadataObjs.every(m => m.open_count > 0),
|
||||||
|
|
|
@ -20,7 +20,7 @@ export default class OpenTrackingMessageStatus extends React.Component {
|
||||||
|
|
||||||
_getStateFromMessage(message) {
|
_getStateFromMessage(message) {
|
||||||
const metadata = message.metadataForPluginId(PLUGIN_ID);
|
const metadata = message.metadataForPluginId(PLUGIN_ID);
|
||||||
if (!metadata) {
|
if (!metadata || metadata.open_count == null) {
|
||||||
return {hasMetadata: false, opened: false}
|
return {hasMetadata: false, opened: false}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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 = `<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;"> On Feb 25 2016, at 3:38 pm, Drew <drew@nylas.com> wrote: <br> twst </blockquote>`;
|
||||||
|
|
||||||
|
describe("Open tracking composer extension", () => {
|
||||||
|
|
||||||
|
// Set up a draft, session that returns the draft, and metadata
|
||||||
|
beforeEach(()=>{
|
||||||
|
this.draft = new Message();
|
||||||
|
this.draft.body = `<body>TEST_BODY ${quote}</body>`;
|
||||||
|
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(/<img .*?>/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has the right server URL", ()=>{
|
||||||
|
const img = this.unquoted.match(/<img .*?>/)[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()
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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(<OpenTrackingIcon {...props} thread={thread} />);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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(<div className="temp"><OpenTrackingMessageStatus {...props} message={message} /></div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Reference in a new issue