Skip to content

Commit

Permalink
Loading manager tests (#52)
Browse files Browse the repository at this point in the history
* adding test coverage to loading manager

* resetting test.js

* fixing comment
  • Loading branch information
chanind authored Feb 7, 2018
1 parent d26f236 commit eafab64
Show file tree
Hide file tree
Showing 7 changed files with 1,895 additions and 779 deletions.
12 changes: 11 additions & 1 deletion babel-jest-processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,17 @@ module.exports = {
return babel.transform(src, {
filename: filename,
retainLines: true,
presets: ['env']
presets: ['env'],
plugins: [
[
"transform-runtime",
{
"helpers": false,
"polyfill": false,
"regenerator": true
}
]
]
}).code;
}

Expand Down
24 changes: 7 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"babel-eslint": "^8.0.1",
"babel-jest": "^21.2.0",
"babel-loader": "^7.1.2",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.0",
"eslint": "^4.8.0",
"eslint-config-airbnb": "^15.1.0",
Expand All @@ -24,7 +25,8 @@
"grunt-contrib-uglify": "^3.1.0",
"grunt-contrib-watch": "^1.0.0",
"grunt-webpack": "^3.0.2",
"jest-cli": "^0.7.1",
"hanzi-writer-data": "^1.0.0",
"jest-cli": "^22.2.1",
"webpack": "^3.6.0",
"webpack-dev-server": "^2.9.1"
},
Expand All @@ -34,21 +36,9 @@
"prepublishOnly": "grunt"
},
"jest": {
"scriptPreprocessor": "<rootDir>/../babel-jest-processor.js",
"preprocessCachingDisabled": true,
"testFileExtensions": [
"js"
],
"moduleFileExtensions": [
"js"
],
"rootDir": "src",
"unmockedModulePathPatterns": [
"models",
"util",
"geometry",
"svg",
"clone"
]
"transform": {
".*": "<rootDir>/../babel-jest-processor.js"
},
"rootDir": "src"
}
}
7 changes: 6 additions & 1 deletion src/HanziWriter.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,12 @@ HanziWriter.prototype._withData = function(func) {
// Try reloading again and see if it helps
if (this._loadingManager.loadingFailed) {
this.setCharacter(this._char);
return this._withData(func);
return Promise.resolve().then(() => {
// check loadingFailed again just in case setCharacter fails synchronously
if (!this._loadingManager.loadingFailed) {
return this._withData(func);
}
});
}
return this._withDataPromise.then(() => {
if (!this._loadingManager.loadingFailed) {
Expand Down
18 changes: 13 additions & 5 deletions src/LoadingManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ function LoadingManager(options) {
this._isLoading = false;
this._loadingPromise = null;

// use this to attribute to determine if there was a problem with loading
// use this to attribute to determine if there was a problem with loading
this.loadingFailed = false;
}

Expand All @@ -16,8 +16,8 @@ LoadingManager.prototype._debouncedLoad = function(char, count) {
const wrappedResolve = (data) => {
if (count === this._loadCounter) this._resolve(data);
};
const wrappedReject = (...args) => {
if (count === this._loadCounter) this._reject(...args);
const wrappedReject = (reason) => {
if (count === this._loadCounter) this._reject(reason);
};

const returnedData = this._options.charDataLoader(char, wrappedResolve, wrappedReject);
Expand All @@ -32,14 +32,22 @@ LoadingManager.prototype._setupLoadingPromise = function() {
this._isLoading = false;
callIfExists(this._options.onLoadCharDataSuccess, data);
return data;
}, (...args) => {
}, (reason) => {
this._isLoading = false;
this.loadingFailed = true;
callIfExists(this._options.onLoadCharDataError, ...args);
callIfExists(this._options.onLoadCharDataError, reason);
// If error callback wasn't provided, throw an error so the developer will be aware something went wrong
if (!this._options.onLoadCharDataError) {
if (reason instanceof Error) throw reason;
const err = new Error(`Failed to load char data for ${this._loadingChar}`);
err.reason = reason;
throw err;
}
});
};

LoadingManager.prototype.loadCharData = function(char) {
this._loadingChar = char;
if (!this._isLoading) {
this._setupLoadingPromise();
}
Expand Down
66 changes: 66 additions & 0 deletions src/__tests__/HanziWriter-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const ren = require('hanzi-writer-data/人.json');
const HanziWriter = require('../HanziWriter');

describe('HanziWriter', () => {
// Hack because JSDom doesn't support SVG well
window.SVGElement.prototype.getTotalLength = () => 10;

describe('constructor', () => {
it("loads data and builds an instance in a dom element", () => {
document.body.innerHTML = '<div id="target"></div>';

const writer = new HanziWriter('target', '人', {
charDataLoader: () => ren,
});

// TODO: add more assertions
expect(document.querySelectorAll('svg').length).toBe(1);
});

it("calls onLoadCharDataError if provided on loading failure", async () => {
document.body.innerHTML = '<div id="target"></div>';

const onLoadCharDataError = jest.fn();
const writer = new HanziWriter('target', '人', {
onLoadCharDataError,
charDataLoader: () => Promise.reject('reasons'),
});

await writer._withDataPromise;

expect(onLoadCharDataError.mock.calls.length).toBe(1);
expect(onLoadCharDataError.mock.calls[0][0]).toBe('reasons');
});

it("tries reloading when calling an animatable method after loading failure", async () => {
document.body.innerHTML = '<div id="target"></div>';

const onLoadCharDataError = jest.fn();
const writer = new HanziWriter('target', '人', {
onLoadCharDataError,
charDataLoader: (char, onComplete, onErr) => {
onErr('reasons');
},
});

await writer._withDataPromise;
await writer.showCharacter();

expect(onLoadCharDataError.mock.calls.length).toBe(2);
expect(onLoadCharDataError.mock.calls[0][0]).toBe('reasons');
expect(onLoadCharDataError.mock.calls[1][0]).toBe('reasons');
});

it("throws an error on loading fauire if onLoadCharDataError is not provided", async () => {
document.body.innerHTML = '<div id="target"></div>';

const writer = new HanziWriter('target', '人', {
charDataLoader: (char, onComplete, onErr) => {
onErr(new Error('reasons'));
},
});

await expect(writer._withDataPromise).rejects.toThrow(new Error('reasons'));
});
});
});
113 changes: 113 additions & 0 deletions src/__tests__/LoadingManager-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
const ren = require('hanzi-writer-data/人.json');
const ta = require('hanzi-writer-data/他.json');
const { timeout } = require('../utils');
const LoadingManager = require('../LoadingManager');

describe('LoadingManager', () => {
describe('loadCharData', () => {
it("resolves when data is loaded via async callback", async () => {
const manager = new LoadingManager({
charDataLoader: (char, onComplete, onErr) => {
setTimeout(() => onComplete(ren), 1);
},
});
const data = await manager.loadCharData('人');
expect(data).toBe(ren);
expect(manager.loadingFailed).toBe(false);
});

it("resolves when data is loaded via sync callback", async () => {
const manager = new LoadingManager({
charDataLoader: (char, onComplete, onErr) => { onComplete(ren); },
});
const data = await manager.loadCharData('人');
expect(data).toBe(ren);
expect(manager.loadingFailed).toBe(false);
});

it("resolves when data is loaded via promise", async () => {
const manager = new LoadingManager({
charDataLoader: (char, onComplete, onErr) => Promise.resolve(ren),
});
const data = await manager.loadCharData('人');
expect(data).toBe(ren);
expect(manager.loadingFailed).toBe(false);
});

it("resolves when data is loaded via sync return", async () => {
const manager = new LoadingManager({
charDataLoader: (char, onComplete, onErr) => ren,
});
const data = await manager.loadCharData('人');
expect(data).toBe(ren);
expect(manager.loadingFailed).toBe(false);
});

it("passes data to onLoadCharDataSuccess if provided", async () => {
let successVal;
const manager = new LoadingManager({
charDataLoader: (char, onComplete, onErr) => ren,
onLoadCharDataSuccess: (returnedData) => successVal = returnedData
});
const data = await manager.loadCharData('人');
expect(data).toBe(ren);
expect(successVal).toBe(ren);
expect(manager.loadingFailed).toBe(false);
});

it("throws an error if loading fails via onErr callback and no callback is provided", async () => {
const manager = new LoadingManager({
charDataLoader: (char, onComplete, onErr) => { onErr('OMG'); },
});
await expect(manager.loadCharData('人')).rejects.toThrow(new Error('Failed to load char data for 人'));
expect(manager.loadingFailed).toBe(true);
});

it("rethrows if loading fails via onErr callback passing an Error and no callback is provided", async () => {
const manager = new LoadingManager({
charDataLoader: (char, onComplete, onErr) => { onErr(new Error('OMG')); },
});
await expect(manager.loadCharData('人')).rejects.toThrow(new Error('OMG'));
expect(manager.loadingFailed).toBe(true);
});

it("resolves if loading fails via onErr callback and a callback is provided", async () => {
let failureReason;
const manager = new LoadingManager({
charDataLoader: (char, onComplete, onErr) => { onErr('everything is terrible'); },
onLoadCharDataError: (reason) => { failureReason = reason; },
});
const data = await manager.loadCharData('人');
expect(manager.loadingFailed).toBe(true);
expect(data).toBe(undefined);
expect(failureReason).toBe('everything is terrible');
});

it("debounces if multiple loads are called at the same time", async () => {
const onLoadCharDataSuccess = jest.fn();
const onCompleteFns = [];
const manager = new LoadingManager({
onLoadCharDataSuccess,
charDataLoader: (char, onComplete) => {
onCompleteFns.push(onComplete);
},
});

const loadPromise1 = manager.loadCharData('人');
const loadPromise2 = manager.loadCharData('他');
// it should return the same promise for both since loading isn't complete
expect(loadPromise1).toBe(loadPromise2);

onCompleteFns[0].call(null, ren);
onCompleteFns[1].call(null, ta);

const data = await loadPromise1;

// ren should be ignored. It's like it was never requested at all
expect(data).toBe(ta);
expect(onLoadCharDataSuccess.mock.calls.length).toBe(1);
expect(onLoadCharDataSuccess.mock.calls[0][0]).toBe(ta);
expect(manager.loadingFailed).toBe(false);
});
});
});
Loading

0 comments on commit eafab64

Please sign in to comment.