Skip to content

Commit

Permalink
Improve mock setup
Browse files Browse the repository at this point in the history
  • Loading branch information
gilmoreg committed Feb 3, 2019
1 parent 550d26c commit 928e543
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 63 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,7 @@ typings/
# Build Output
dist/

notes
notes

# Vs Code Go tools keep creating this; exclude it
go.mod
134 changes: 134 additions & 0 deletions __mocks__/MockMAL.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import * as fetchMock from 'fetch-mock';
import * as T from '../src/Types';
import * as fakes from '../__testutils__/testData';

const defaultAnime: T.MALLoadAnime[] = [fakes.createFakeMALAnime()];
const defaultManga: T.MALLoadManga[] = [fakes.createFakeMALManga()];

// https://myanimelist.net/${type}list/${this.username}/load.json?offset=${offset}&status=7
const loadRegex = /^https:\/\/myanimelist\.net\/(.+)list\/.+\/load\.json\?offset=(.+)&status=7$/;

// https://myanimelist.net/ownlist/${type}/${id}/edit?hideLayout
const editRegex = /^https:\/\/myanimelist\.net\/ownlist\/(.+)\/(.+)\/edit\?hideLayout$/;

// https://myanimelist.net/ownlist/${data.type}/add.json
const addRegex = /^https:\/\/myanimelist\.net\/ownlist\/(.+)\/add\.json$/;

const parseFormData = (formData: string) => {
const result: any = {};
formData.split('&').forEach(i => {
const [key, value] = i.split('=');
const parsedKey = key.replace(/%5B/g, '[').replace(/%5D/g, ']');
result[parsedKey] = value;
});
return result;
}

const formDataToLoadAnime = (f: T.MALAnimeFormData, e: T.MALLoadAnime): T.MALLoadAnime => {
const entry: T.MALLoadAnime = e || fakes.createFakeMALAnime();
return {
anime_id: Number(f.anime_id),
num_watched_episodes: Number(f['add_anime[num_watched_episodes]']),
anime_num_episodes: entry.anime_num_episodes,
anime_airing_status: entry.anime_airing_status,
finish_date_string: `${f["add_anime[finish_date][day]"]}-${f['add_anime[finish_date][month]']}-${f['add_anime[finish_date][year]']}`,
start_date_string: `${f["add_anime[start_date][day]"]}-${f['add_anime[start_date][month]']}-${f['add_anime[start_date][year]']}`,
priority_string: `${f['add_anime[priority]']}`,
comments: f['add_anime[comments]'],
score: Number(f['add_anime[score]']),
csrf_token: entry.csrf_token,
status: Number(f['add_anime[status]']),
}
};

const formDataToLoadManga = (f: T.MALMangaFormData, e: T.MALLoadManga): T.MALLoadManga => {
const entry: T.MALLoadManga = e || fakes.createFakeMALManga();
return {
manga_id: Number(f.manga_id),
num_read_chapters: Number(f['add_manga[num_read_chapters]']),
num_read_volumes: Number(f['add_manga[num_read_volumes]']),
manga_num_chapters: entry.manga_num_chapters,
manga_num_volumes: entry.manga_num_volumes,
manga_publishing_status: entry.manga_publishing_status,
finish_date_string: `${f["add_manga[finish_date][day]"]}-${f['add_manga[finish_date][month]']}-${f['add_manga[finish_date][year]']}`,
start_date_string: `${f["add_manga[start_date][day]"]}-${f['add_manga[start_date][month]']}-${f['add_manga[start_date][year]']}`,
priority_string: `${f['add_manga[priority]']}`,
comments: f['add_manga[comments]'],
score: Number(f['add_manga[score]']),
csrf_token: entry.csrf_token,
status: Number(f['add_manga[status]']),
};
}

class MockMAL {
anime: T.MALLoadAnime[]
manga: T.MALLoadManga[]

constructor(anime: T.MALLoadAnime[] = defaultAnime, manga: T.MALLoadManga[] = defaultManga) {
this.anime = anime;
this.manga = manga;

this.load = this.load.bind(this);
this.add = this.add.bind(this);
this.edit = this.edit.bind(this);

fetchMock.mock(/.+load.json.+/, url => this.load(url));
fetchMock.mock(/.+add.+/, (url, opts) => this.add(url, opts));
fetchMock.mock(/.+edit.+/, (url, opts) => this.edit(url, opts));
}

load(url: string): T.MALLoadItem[] {
// @ts-ignore
const [_, listType, offset] = loadRegex.exec(url);
if (Number(offset) > 0) return [];

return listType === 'anime' ? this.anime : this.manga;
}

add(url: string, opts: any): string {
// @ts-ignore
const [_, type] = addRegex.exec(url);

const entry = JSON.parse(opts.body);

if (type === 'anime') {
this.anime.push(entry);
} else {
this.manga.push(entry);
}

return ' Successfully added entry ';
}

edit(url: string, opts: any): string {
// @ts-ignore
const [_, listType, id] = editRegex.exec(url);
const nid = Number(id);
const entry = listType === 'anime' ?
this.anime.find(i => i.anime_id === nid) :
this.manga.find(i => i.manga_id === nid);
if (!entry) return ' Not found ';

const formData = parseFormData(opts.body);

if (listType === 'anime') {
this.anime = this.anime.map(i => {
if (i.anime_id === nid) {
return formDataToLoadAnime(formData, entry as T.MALLoadAnime);
}
return i;
});
} else {
this.manga = this.manga.map(i => {
if (i.manga_id === nid) {
return formDataToLoadManga(formData, entry as T.MALLoadManga);
}
return i;
});
}

return ' Successfully updated entry ';
}
}

export default MockMAL;
48 changes: 16 additions & 32 deletions __tests__/MAL.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@ import MAL from '../src/MAL';
import FakeLog from '../__mocks__/Log';
import * as fetchMock from 'fetch-mock';
import * as fakes from '../__testutils__/testData';
import { MALLoadItem } from '../src/Types';
import MockMAL from '../__mocks__/MockMAL'

const mockGetMALList = (list: Array<MALLoadItem>) => {
fetchMock
.mock(/.+load.json.+/, list, { repeat: 1 })
.mock(/.+load.json.+/, []);
}
const createFakeMAL = () => new MAL('test', 'csrfToken', new FakeLog(), fakes.createFakeDomMethods());

describe('syncType()', () => {
beforeAll(() => fetchMock.catch(500));
afterEach(() => fetchMock.restore());

it('should skip sync when items are the same', async () => {
mockGetMALList([fakes.createFakeMALAnime()]);
const mal = new MAL('test', 'csrfToken');
new MockMAL();
const mal = new MAL('test', 'csrfToken', new FakeLog(), fakes.createFakeDomMethods());
await mal.syncType('anime', [fakes.createFakeAnilistAnime()]);
// Two calls to load list, no refresh, no calls to edit
expect(fetchMock.calls().length).toBe(2);
Expand All @@ -25,42 +21,30 @@ describe('syncType()', () => {
it('should sync when episode count is different', async () => {
const malAnime = fakes.createFakeMALAnime({ status: 1, num_watched_episodes: 1 });
const alAnime = fakes.createFakeAnilistAnime({ status: 'CURRENT', progress: 2 });
fetchMock
.once(/.+load.json.+/, [malAnime])
.once(/.+load.json.+/, [])
.once(/.+edit.+/, ' Successfully updated entry ');
const mal = new MAL('test', 'csrfToken', new FakeLog());
const mockMAL = new MockMAL([malAnime]);
const mal = createFakeMAL();
await mal.syncType('anime', [alAnime]);
const [url] = fetchMock.calls()[2];
expect(url).toEqual('https://myanimelist.net/ownlist/anime/1/edit?hideLayout');
const [result] = mockMAL.anime;
expect(result.num_watched_episodes).toEqual(2);
});

it('should add a new manga', async () => {
const malManga = fakes.createFakeMALManga();
const alManga = fakes.createFakeAnilistManga();
fetchMock
.once(/.+load.json.+/, [])
.once(/.+add.json/, {})
.once(/.+load.json.+/, [malManga])
.once(/.+load.json.+/, []);
const mal = new MAL('test', 'csrfToken', new FakeLog());
const mockMAL = new MockMAL();
const mal = createFakeMAL();
await mal.syncType('manga', [alManga]);
const [url] = fetchMock.calls()[1];
expect(url).toEqual('https://myanimelist.net/ownlist/manga/add.json');
const [result] = mockMAL.manga;
expect(result).toEqual(malManga);
});

it('should sync a manga when volume count is different', async () => {
const malManga = fakes.createFakeMALManga({ status: 1, manga_num_volumes: 2, num_read_volumes: 1 });
const alManga = fakes.createFakeAnilistManga({ status: 'CURRENT', progressVolumes: 2 });
fetchMock
.once(/.+load.json.+/, [malManga])
.once(/.+load.json.+/, [])
.once(/.+edit.+/, ' Successfully updated entry ')
.once(/.+load.json.+/, [fakes.createFakeMALManga({ num_read_volumes: 2 })])
.once(/.+load.json.+/, []);
const mal = new MAL('test', 'csrfToken', new FakeLog());
const mockMAL = new MockMAL(undefined, [malManga]);
const mal = createFakeMAL();
await mal.syncType('manga', [alManga]);
const [url] = fetchMock.calls()[2];
expect(url).toEqual('https://myanimelist.net/ownlist/manga/2/edit?hideLayout');
const [result] = mockMAL.manga;
expect(result.num_read_volumes).toEqual(2);
});
});
1 change: 0 additions & 1 deletion __tests__/MALEntry.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { MALEntryAnime, MALEntryManga } from '../src/MALEntry';
import * as fakes from '../__testutils__/testData';
import { AssertionError } from 'assert';

const fakeDomMethods = fakes.createFakeDomMethods();

Expand Down
28 changes: 14 additions & 14 deletions __tests__/__snapshots__/MALEntry.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@

exports[`formData() should return default values of correct types for anime 1`] = `
Object {
"add_anime%5Bcomments%5D": "0",
"add_anime%5Bfinish_date%5D%5Bday%5D": "",
"add_anime%5Bfinish_date%5D%5Bmonth%5D": "",
"add_anime%5Bfinish_date%5D%5Byear%5D": "",
"add_anime%5Bcomments%5D": "comments",
"add_anime%5Bfinish_date%5D%5Bday%5D": "1",
"add_anime%5Bfinish_date%5D%5Bmonth%5D": "1",
"add_anime%5Bfinish_date%5D%5Byear%5D": "99",
"add_anime%5Bis_asked_to_discuss%5D": "0",
"add_anime%5Bnum_watched_episodes%5D": "12",
"add_anime%5Bnum_watched_times%5D": "1",
"add_anime%5Bpriority%5D": "0",
"add_anime%5Brewatch_value%5D": "0",
"add_anime%5Bscore%5D": "10",
"add_anime%5Bsns_post_type%5D": "0",
"add_anime%5Bstart_date%5D%5Bday%5D": "",
"add_anime%5Bstart_date%5D%5Bmonth%5D": "",
"add_anime%5Bstart_date%5D%5Byear%5D": "",
"add_anime%5Bstart_date%5D%5Bday%5D": "1",
"add_anime%5Bstart_date%5D%5Bmonth%5D": "1",
"add_anime%5Bstart_date%5D%5Byear%5D": "99",
"add_anime%5Bstatus%5D": "2",
"add_anime%5Bstorage_type%5D": "0",
"add_anime%5Bstorage_value%5D": "0",
Expand All @@ -30,10 +30,10 @@ Object {

exports[`formData() should return default values of correct types for manga 1`] = `
Object {
"add_manga%5Bcomments%5D": "0",
"add_manga%5Bfinish_date%5D%5Bday%5D": "",
"add_manga%5Bfinish_date%5D%5Bmonth%5D": "",
"add_manga%5Bfinish_date%5D%5Byear%5D": "",
"add_manga%5Bcomments%5D": "comments",
"add_manga%5Bfinish_date%5D%5Bday%5D": "1",
"add_manga%5Bfinish_date%5D%5Bmonth%5D": "1",
"add_manga%5Bfinish_date%5D%5Byear%5D": "99",
"add_manga%5Bis_asked_to_discuss%5D": "0",
"add_manga%5Bnum_read_chapters%5D": "12",
"add_manga%5Bnum_read_times%5D": "1",
Expand All @@ -43,9 +43,9 @@ Object {
"add_manga%5Breread_value%5D": "0",
"add_manga%5Bscore%5D": "10",
"add_manga%5Bsns_post_type%5D": "0",
"add_manga%5Bstart_date%5D%5Bday%5D": "",
"add_manga%5Bstart_date%5D%5Bmonth%5D": "",
"add_manga%5Bstart_date%5D%5Byear%5D": "",
"add_manga%5Bstart_date%5D%5Bday%5D": "1",
"add_manga%5Bstart_date%5D%5Bmonth%5D": "1",
"add_manga%5Bstart_date%5D%5Byear%5D": "99",
"add_manga%5Bstatus%5D": "2",
"add_manga%5Bstorage_type%5D": "0",
"add_manga%5Btags%5D": "",
Expand Down
4 changes: 2 additions & 2 deletions __testutils__/setupJest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ global.fetch = fetch;
class FakeDOM {
querySelector(selector: string) {
switch (selector) {
case 'add_anime_comments':
case 'add_manga_comments':
case '#add_anime_comments':
case '#add_manga_comments':
return {
value: 'comments'
}
Expand Down
17 changes: 10 additions & 7 deletions __testutils__/testData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ const malItem: Types.BaseMALItem = {
status: 2,
csrf_token: 'csrfToken',
score: 10,
finish_date_string: null,
start_date_string: null,
finish_date_string: '01-01-99',
start_date_string: '01-01-99',
priority_string: '0',
comments: 'comments',
};

const malAnime = {
Expand All @@ -25,7 +27,8 @@ const malManga = {
manga_num_chapters: 12,
manga_num_volumes: 1,
num_read_chapters: 12,
num_read_volumes: 1
num_read_volumes: 1,
manga_publishing_status: 0,
} as Types.MALLoadManga;

export const createFakeMALAnime = (data: any = {}) =>
Expand All @@ -38,8 +41,8 @@ const alAnime: Types.FormattedEntry = {
score: 10,
progress: 12,
progressVolumes: 0,
startedAt: createDate(),
completedAt: createDate(),
startedAt: createDate(99, 1, 1),
completedAt: createDate(99, 1, 1),
repeat: 1,
id: malAnime.anime_id,
title: 'title',
Expand All @@ -51,8 +54,8 @@ const alManga: Types.FormattedEntry = {
score: 10,
progress: 12,
progressVolumes: 1,
startedAt: createDate(),
completedAt: createDate(),
startedAt: createDate(99, 1, 1),
completedAt: createDate(99, 1, 1),
repeat: 1,
id: malManga.manga_id,
title: 'title',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,4 @@
]
},
"dependencies": {}
}
}
7 changes: 5 additions & 2 deletions src/MAL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ import { sleep } from './util';
import { MALHashMap, FormattedEntry, MALLoadItem } from './Types';
import { MALEntry, createMALEntry } from './MALEntry';
import Log, { ILog } from './Log';
import Dom, { IDomMethods } from "./Dom";

export default class MAL {
username: string
csrfToken: string
Log: ILog
dom: IDomMethods

constructor(username: string, csrfToken: string, log: ILog = Log) {
constructor(username: string, csrfToken: string, log: ILog = Log, dom: IDomMethods = Dom) {
this.username = username;
this.csrfToken = csrfToken;
this.Log = log;
this.dom = dom;
}

private createMALHashMap(malList: Array<MALLoadItem>, type: string): MALHashMap {
Expand Down Expand Up @@ -42,7 +45,7 @@ export default class MAL {

private async getEntriesList(anilistList: Array<FormattedEntry>, type: string): Promise<Array<MALEntry>> {
const malHashMap = await this.getMALHashMap(type);
return anilistList.map(entry => createMALEntry(entry, malHashMap[entry.id], this.csrfToken));
return anilistList.map(entry => createMALEntry(entry, malHashMap[entry.id], this.csrfToken, this.dom));
}

private async malEdit(data: MALEntry) {
Expand Down
6 changes: 3 additions & 3 deletions src/MALEntry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import * as T from "./Types";
import { MALForm } from './MALForm';
import Dom, { IDomMethods } from "./Dom";

export const createMALEntry = (al: T.FormattedEntry, mal: T.MALLoadItem, csrfToken: string) =>
export const createMALEntry = (al: T.FormattedEntry, mal: T.MALLoadItem, csrfToken: string, dom: IDomMethods) =>
al.type === 'anime' ?
new MALEntryAnime(al, mal, csrfToken) :
new MALEntryManga(al, mal, csrfToken);
new MALEntryAnime(al, mal, csrfToken, dom) :
new MALEntryManga(al, mal, csrfToken, dom);

type StringNumMap = { [key: string]: number }
const MALStatus: StringNumMap = {
Expand Down

0 comments on commit 928e543

Please sign in to comment.