diff --git a/README.md b/README.md index eb33720..08b2345 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ WordPOS.defaults = { * include data files in preload * @type {boolean} */ - includeData: false, // WIP + includeData: false, /** * set to true to enable debug logging @@ -316,10 +316,6 @@ For CLI usage and examples, see [bin/README](bin). See [bench/README](bench). -## TODO -- implement `includeData` option for preload - - ## Changes See [CHANGELOG](./CHANGELOG.md). diff --git a/package.json b/package.json index 43e8db1..0a55726 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wordpos", - "version": "2.0.0-beta.11", + "version": "2.0.0-beta.13", "description": "wordpos is a set of part-of-speech utilities for Node.js & browser using the WordNet database.", "author": "Moos ", "keywords": [ @@ -69,8 +69,8 @@ "prepare": "npm run build", "pretest": "node tools/makeJsonDict.js index data", "test": "npm run test-node && npm run test-browser", - "test-node": "mocha test", - "test-browser": "mocha test/wordpos_test --require @babel/register" + "test-node": "mocha test --exclude test/browser_test.js", + "test-browser": "mocha test/browser_test test/wordpos_test --require @babel/register" }, "license": "MIT" } diff --git a/src/browser/baseFile.js b/src/browser/baseFile.js index abce7c6..889e3b5 100644 --- a/src/browser/baseFile.js +++ b/src/browser/baseFile.js @@ -44,7 +44,8 @@ class BaseFile { this.options.debug && console.timeEnd('index load ' + this.posName) return promise .then(exports => { - this.file = exports.default + this.file = exports.default; + return this; }) .catch(err => { console.error(`Error loading "${this.type}" file ${this.filePath}.`, err); @@ -54,7 +55,7 @@ class BaseFile { } ready(fn, args) { - return this.load().then(() => fn.apply(this, args)); + return this.load().then(res => fn && fn.apply(this, args) || res); } } diff --git a/src/browser/index.js b/src/browser/index.js index 131a539..c2f6cdc 100644 --- a/src/browser/index.js +++ b/src/browser/index.js @@ -34,6 +34,10 @@ class WordPOS { } } + ready() { + return this.loaded || Promise.resolve(); + } + initFiles() { const keys = Object.keys(POS); const loadOne = (Comp, pos) => new Comp(this.options.dictPath, POS[pos], this.options); @@ -44,7 +48,7 @@ class WordPOS { this.dataFiles = reducer(loader(DataFile)); if (this.options.preload) { - this.loaded = this.preloadIndexes(this.options.preload); + this.loaded = this.preloadFiles(this.options.preload); } } @@ -61,23 +65,28 @@ class WordPOS { * @param {string|Array} [pos] POS to load (default: all) * @return {Promise.} */ - preloadIndexes(pos) { - let file = this.indexFile[pos]; - let load = p => file.load(); + preloadFiles(pos) { + let promise = this._preload(this.indexFiles, pos); + if (this.options.includeData) { + promise = Promise.all([].concat(promise, this._preload(this.dataFiles, pos))) + .then(res => res.flat()); + } + return promise; + } + + _preload(files, pos) { + let load = p => files[p].load(); let promise; if (!pos || pos === true) { // preload all promise = Promise.all(Object.keys(POS).map(load)); } - else if (typeof pos === 'string' && file) { + else if (typeof pos === 'string' && files[pos]) { promise = load(pos); } else if (pos instanceof Array) { - promise = pos.forEach(pos => file && load(pos)); + promise = Promise.all(pos.map(load)); } - - // TODO includeData - return promise || Promise.reject(new RangeError(`Unknown POS "${pos}" for preload.`)); } @@ -183,6 +192,8 @@ WordPOS.defaults = { */ WordPOS.stopwords = stopwords; +WordPOS.POS = POS; + // Export as CJS handled by Parcel, otherwise will get WordPOS.default // if use: export default WordPOS; module.exports = WordPOS; diff --git a/test/browser_test.js b/test/browser_test.js new file mode 100644 index 0000000..d9ba8d7 --- /dev/null +++ b/test/browser_test.js @@ -0,0 +1,237 @@ +/** + * browser_test.js + * + * test file for browser-specific functionality + * + * Usage: + * npm install mocha -g + * mocha browser_test.js -require @babel/register + * + * or + * + * npm test + * + * Copyright (c) 2012-2020 mooster@42at.com + * https://github.com/moos/wordpos + * + * Released under MIT license + */ + + // used in src code to signal test mode +global.window = global.window || {}; +global.window.__mocha = true; + +var + chai = require('chai'), + _ = require('underscore'), + assert = chai.assert, + browser = process.browser = process.argv.includes('@babel/register'), + WordPOS = require('../src/wordpos'), + wordpos, + path = require('path'), + dictPath = browser ? path.resolve('./test/dict') : undefined; + +const dictRequired = () => Object.keys(require.cache).filter(p => /\bdict\b/i.test(p)); + +if (!browser) { + throw new Error('Not in browser mode!'); +} +chai.config.showDiff = true; + +describe('options: preload', () => { + + // clear require.cache before each test + beforeEach(() => { + dictRequired().forEach((m) => delete require.cache[m]); + }); + + it('preload: false', () => { + wordpos = new WordPOS({ + preload: false, + dictPath: dictPath + }); + return wordpos.ready().then(res => { + assert.equal(res, undefined); + assert.equal(dictRequired().length, 0); + }); + }); + + it('preload: true', () => { + assert.equal(dictRequired().length, 0); + wordpos = new WordPOS({ + preload: true, + dictPath: dictPath + }); + return wordpos.ready().then(res => { + assert.equal(res.length, 4); + res.forEach(index => assert.equal(index.type, 'index')); + res.forEach(index => assert.equal(/\bdict.index\./.test(index.filePath), true)); + + let reqs = dictRequired(); + assert.equal(reqs.length, 4); + reqs.forEach(req => assert.equal(/\bdict.index\./.test(req), true)); + Object.values(WordPOS.POS).forEach(pos => assert.notEqual(reqs.join().indexOf(`index.${pos}.js`), -1)); + }); + }); + + it('preload: "r"', () => { + wordpos = new WordPOS({ + preload: 'r', + dictPath: dictPath + }); + return wordpos.ready().then(res => { + assert.equal(res.type, 'index'); + assert.equal(res.posName, 'adv'); + let reqs = dictRequired(); + assert.equal(reqs.length, 1); + assert.equal(/index\.adv\.js/.test(reqs[0]), true); + }); + }); + + it('preload: ["r","a"]', () => { + wordpos = new WordPOS({ + preload: ['r','a'], + dictPath: dictPath + }); + return wordpos.ready().then(res => { + assert.equal(res.length, 2); + + // TODO -- order may NOT be always the same!!! + assert.equal(res[0].type, 'index'); + assert.equal(res[0].posName, 'adv'); + assert.equal(res[1].type, 'index'); + assert.equal(res[1].posName, 'adj'); + + let reqs = dictRequired(); + assert.equal(reqs.length, 2); + assert.equal(/index\.adv\.js/.test(reqs[0]), true); + assert.equal(/index\.adj\.js/.test(reqs[1]), true); + }); + }); + + it('preload: "foo"', () => { + wordpos = new WordPOS({ + preload: 'foo', + dictPath: dictPath + }); + return wordpos.ready().then(res => { + // shouldn't get here + assert(false); + }).catch(err => { + assert.equal(err, 'RangeError: Unknown POS "foo" for preload.') + }); + }); +}); + + + +describe('options: preload with includeData', () => { + + // clear require.cache before each test + beforeEach(() => { + dictRequired().forEach((m) => delete require.cache[m]); + }); + + it('preload: false', () => { + wordpos = new WordPOS({ + preload: false, + includeData: true, + dictPath: dictPath + }); + return wordpos.ready().then(res => { + assert.equal(res, undefined); + assert.equal(dictRequired().length, 0); + }); + }); + + it('preload: true', () => { + assert.equal(dictRequired().length, 0); + wordpos = new WordPOS({ + preload: true, + includeData: true, + dictPath: dictPath + }); + return wordpos.ready().then(res => { + assert.equal(res.length, 8); + assert.equal(res.filter(m => m.type === 'index').length, 4); + assert.equal(res.filter(m => m.type === 'data').length, 4); + assert.equal(res.filter(m => /\bdict.index\./.test(m.filePath)).length, 4); + assert.equal(res.filter(m => /\bdict.data\./.test(m.filePath)).length, 4); + + let reqs = dictRequired(); + assert.equal(reqs.length, 8); + assert.equal(reqs.filter(m => /\bdict.index\./.test(m)).length, 4); + assert.equal(reqs.filter(m => /\bdict.data\./.test(m)).length, 4); + + let reqsStr = reqs.join(); + Object.values(WordPOS.POS).forEach(pos => { + assert.equal(reqs.filter(m => m.indexOf(`index.${pos}.js`) !== -1).length, 1); + assert.equal(reqs.filter(m => m.indexOf(`data.${pos}.js`) !== -1).length, 1); + }); + }); + }); + + it('preload: "r"', () => { + wordpos = new WordPOS({ + preload: 'r', + includeData: true, + dictPath: dictPath + }); + return wordpos.ready().then(res => { + assert.equal(res.length, 2); + assert.equal(res[0].type, 'index'); + assert.equal(res[0].posName, 'adv'); + assert.equal(res[1].type, 'data'); + assert.equal(res[1].posName, 'adv'); + let reqs = dictRequired(); + assert.equal(reqs.length, 2); + assert.equal(/index\.adv\.js/.test(reqs[0]), true); + assert.equal(/data\.adv\.js/.test(reqs[1]), true); + }); + }); + + it('preload: ["r","a"]', () => { + wordpos = new WordPOS({ + preload: ['r','a'], + includeData: true, + dictPath: dictPath + }); + return wordpos.ready().then(res => { + assert.equal(res.length, 4); + + // TODO -- order may NOT be always the same!!! + assert.equal(res[0].type, 'index'); + assert.equal(res[0].posName, 'adv'); + assert.equal(res[1].type, 'index'); + assert.equal(res[1].posName, 'adj'); + + assert.equal(res[2].type, 'data'); + assert.equal(res[2].posName, 'adv'); + assert.equal(res[3].type, 'data'); + assert.equal(res[3].posName, 'adj'); + + let reqs = dictRequired(); + assert.equal(reqs.length, 4); + assert.equal(/index\.adv\.js/.test(reqs[0]), true); + assert.equal(/index\.adj\.js/.test(reqs[1]), true); + assert.equal(/data\.adv\.js/.test(reqs[2]), true); + assert.equal(/data\.adj\.js/.test(reqs[3]), true); + }); + }); + + it('preload: "foo"', () => { + wordpos = new WordPOS({ + preload: 'foo', + includeData: true, + dictPath: dictPath + }); + return wordpos.ready().then(res => { + // shouldn't get here + assert(false); + }).catch(err => { + assert.equal(err, 'RangeError: Unknown POS "foo" for preload.') + }); + }); + + +});