UNPKG

9.31 kBJavaScriptView Raw
1/**
2 * This file contains helper methods to create expected snapshot structures
3 * of both instance and ES6 exports.
4 *
5 * The files are located here and not under /test or /tools so it's transpiled
6 * into ES5 code under /lib and can be used straight by node.js
7 */
8import assert from 'assert'
9import * as allIsFunctions from './is'
10import { create } from '../core/create'
11import { endsWith } from './string'
12
13export function validateBundle (expectedBundleStructure, bundle) {
14 const originalWarn = console.warn
15
16 console.warn = function (...args) {
17 if (args.join(' ').indexOf('is moved to') !== -1 && args.join(' ').indexOf('Please use the new location instead') !== -1) {
18 // Ignore warnings like:
19 // Warning: math.type.isNumber is moved to math.isNumber in v6.0.0. Please use the new location instead.
20 return
21 }
22
23 originalWarn.apply(console, args)
24 }
25
26 try {
27 const issues = []
28
29 // see whether all expected functions and objects are there
30 traverse(expectedBundleStructure, (expectedType, path) => {
31 const actualValue = get(bundle, path)
32 const actualType = validateTypeOf(actualValue)
33
34 const message = (actualType === 'undefined')
35 ? 'Missing entry in bundle. ' +
36 `Path: ${JSON.stringify(path)}, expected type: ${expectedType}, actual type: ${actualType}`
37 : 'Unexpected entry type in bundle. ' +
38 `Path: ${JSON.stringify(path)}, expected type: ${expectedType}, actual type: ${actualType}`
39
40 if (actualType !== expectedType) {
41 issues.push({ actualType, expectedType, message })
42
43 console.warn(message)
44 }
45 })
46
47 // see whether there are any functions or objects that shouldn't be there
48 traverse(bundle, (actualValue, path) => {
49 const actualType = validateTypeOf(actualValue)
50 const expectedType = get(expectedBundleStructure, path) || 'undefined'
51
52 // FIXME: ugly to have these special cases
53 if (path.join('.').indexOf('docs.') !== -1) {
54 // ignore the contents of docs
55 return
56 }
57 if (path.join('.').indexOf('all.') !== -1) {
58 // ignore the contents of all dependencies
59 return
60 }
61
62 const message = (expectedType === 'undefined')
63 ? 'Unknown entry in bundle. ' +
64 'Is there a new function added which is missing in this snapshot test? ' +
65 `Path: ${JSON.stringify(path)}, expected type: ${expectedType}, actual type: ${actualType}`
66 : 'Unexpected entry type in bundle. ' +
67 `Path: ${JSON.stringify(path)}, expected type: ${expectedType}, actual type: ${actualType}`
68
69 if (actualType !== expectedType) {
70 issues.push({ actualType, expectedType, message })
71
72 console.warn(message)
73 }
74 })
75
76 // assert on the first issue (if any)
77 if (issues.length > 0) {
78 const { actualType, expectedType, message } = issues[0]
79
80 console.warn(`${issues.length} bundle issues found`)
81
82 assert.strictEqual(actualType, expectedType, message)
83 }
84 } finally {
85 console.warn = originalWarn
86 }
87}
88
89/**
90 * Based on an object with factory functions, create the expected
91 * structures for ES6 export and a mathjs instance.
92 * @param {Object} factories
93 * @return {{expectedInstanceStructure: Object, expectedES6Structure: Object}}
94 */
95export function createSnapshotFromFactories (factories) {
96 const math = create(factories)
97
98 const allFactoryFunctions = {}
99 const allFunctionsConstantsClasses = {}
100 const allFunctionsConstants = {}
101 const allTransformFunctions = {}
102 const allDependencyCollections = {}
103 const allClasses = {}
104 const allNodeClasses = {}
105
106 Object.keys(factories).forEach(factoryName => {
107 const factory = factories[factoryName]
108 const name = factory.fn
109 const isTransformFunction = factory.meta && factory.meta.isTransformFunction
110 const isClass = !isLowerCase(name[0]) && (validateTypeOf(math[name]) === 'Function')
111 const dependenciesName = factory.fn +
112 (isTransformFunction ? 'Transform' : '') +
113 'Dependencies'
114
115 allFactoryFunctions[factoryName] = 'Function'
116 allFunctionsConstantsClasses[name] = validateTypeOf(math[name])
117 allDependencyCollections[dependenciesName] = 'Object'
118
119 if (isTransformFunction) {
120 allTransformFunctions[name] = 'Function'
121 }
122
123 if (isClass) {
124 if (endsWith(name, 'Node')) {
125 allNodeClasses[name] = 'Function'
126 } else {
127 allClasses[name] = 'Function'
128 }
129 } else {
130 allFunctionsConstants[name] = validateTypeOf(math[name])
131 }
132 })
133
134 let embeddedDocs = {}
135 Object.keys(factories).forEach(factoryName => {
136 const factory = factories[factoryName]
137 const name = factory.fn
138
139 if (isLowerCase(factory.fn[0])) { // ignore class names starting with upper case
140 embeddedDocs[name] = 'Object'
141 }
142 })
143 embeddedDocs = exclude(embeddedDocs, [
144 'equalScalar',
145 'apply',
146 'addScalar',
147 'multiplyScalar',
148 'eye',
149 'print',
150 'divideScalar',
151 'parse',
152 'compile',
153 'parser',
154 'chain',
155 'reviver'
156 ])
157
158 const allTypeChecks = {}
159 Object.keys(allIsFunctions).forEach(name => {
160 if (name.indexOf('is') === 0) {
161 allTypeChecks[name] = 'Function'
162 }
163 })
164
165 const allErrorClasses = {
166 ArgumentsError: 'Function',
167 DimensionError: 'Function',
168 IndexError: 'Function'
169 }
170
171 const expectedInstanceStructure = {
172 ...allFunctionsConstantsClasses,
173
174 on: 'Function',
175 off: 'Function',
176 once: 'Function',
177 emit: 'Function',
178 import: 'Function',
179 var: 'Function',
180 eval: 'Function',
181 typeof: 'Function',
182 config: 'Function',
183 create: 'Function',
184 factory: 'Function',
185
186 ...allTypeChecks,
187 ...allErrorClasses,
188
189 expression: {
190 transform: {
191 ...allTransformFunctions
192 },
193 mathWithTransform: {
194 // note that we don't have classes here,
195 // only functions and constants are allowed in the editor
196 ...exclude(allFunctionsConstants, [
197 'chain'
198 ]),
199 config: 'Function'
200 },
201 // deprecated stuff:
202 // docs: embeddedDocs,
203 node: {
204 ...allNodeClasses
205 },
206 parse: 'Function',
207 Parser: 'Function'
208 },
209
210 // deprecated stuff:
211 type: {
212 ...allTypeChecks,
213 ...allClasses
214 },
215 json: {
216 reviver: 'Function'
217 },
218 error: {
219 ...allErrorClasses
220 }
221 }
222
223 const expectedES6Structure = {
224 // functions
225 ...exclude(allFunctionsConstantsClasses, [
226 'typeof',
227 'eval',
228 'var',
229 'E',
230 'false',
231 'Infinity',
232 'NaN',
233 'null',
234 'PI',
235 'true'
236 ]),
237 create: 'Function',
238 config: 'Function',
239 factory: 'Function',
240 deprecatedEval: 'Function',
241 deprecatedImport: 'Function',
242 deprecatedVar: 'Function',
243 deprecatedTypeof: 'Function',
244 _true: 'boolean',
245 _false: 'boolean',
246 _null: 'null',
247 _Infinity: 'number',
248 _NaN: 'number',
249
250 ...allTypeChecks,
251 ...allErrorClasses,
252 ...allDependencyCollections,
253 ...allFactoryFunctions,
254
255 docs: embeddedDocs,
256
257 // deprecated stuff:
258 expression: {
259 node: {
260 ...allNodeClasses
261 },
262 parse: 'Function',
263 Parser: 'Function'
264 },
265 type: {
266 ...allTypeChecks,
267 ...allClasses
268 },
269 json: {
270 reviver: 'Function'
271 },
272 error: {
273 ...allErrorClasses
274 }
275 }
276
277 return {
278 expectedInstanceStructure,
279 expectedES6Structure
280 }
281}
282
283export function validateTypeOf (x) {
284 if (x && x.type === 'Unit') {
285 return 'Unit'
286 }
287
288 if (x && x.type === 'Complex') {
289 return 'Complex'
290 }
291
292 if (Array.isArray(x)) {
293 return 'Array'
294 }
295
296 if (x === null) {
297 return 'null'
298 }
299
300 if (typeof x === 'function') {
301 return 'Function'
302 }
303
304 if (typeof x === 'object') {
305 return 'Object'
306 }
307
308 return typeof x
309}
310
311function traverse (obj, callback = (value, path) => {}, path = []) {
312 // FIXME: ugly to have these special cases
313 if (path.length > 0 && path[0].indexOf('Dependencies') !== -1) {
314 // special case for objects holding a collection of dependencies
315 callback(obj, path)
316 } else if (validateTypeOf(obj) === 'Array') {
317 obj.map((item, index) => traverse(item, callback, path.concat(index)))
318 } else if (validateTypeOf(obj) === 'Object') {
319 Object.keys(obj).forEach(key => {
320 // FIXME: ugly to have these special cases
321 // ignore special case of deprecated docs
322 if (key === 'docs' && path.join('.') === 'expression') {
323 return
324 }
325
326 traverse(obj[key], callback, path.concat(key))
327 })
328 } else {
329 callback(obj, path)
330 }
331}
332
333function get (object, path) {
334 let child = object
335
336 for (let i = 0; i < path.length; i++) {
337 const key = path[i]
338 child = child ? child[key] : undefined
339 }
340
341 return child
342}
343
344/**
345 * Create a copy of the provided `object` and delete
346 * all properties listed in `excludedProperties`
347 * @param {Object} object
348 * @param {string[]} excludedProperties
349 * @return {Object}
350 */
351function exclude (object, excludedProperties) {
352 const strippedObject = Object.assign({}, object)
353
354 excludedProperties.forEach(excludedProperty => {
355 delete strippedObject[excludedProperty]
356 })
357
358 return strippedObject
359}
360
361function isLowerCase (text) {
362 return typeof text === 'string' && text.toLowerCase() === text
363}
364
\No newline at end of file