diff --git a/packages/core/src/part.mjs b/packages/core/src/part.mjs index a6e467af789..e273b4f7a32 100644 --- a/packages/core/src/part.mjs +++ b/packages/core/src/part.mjs @@ -44,6 +44,24 @@ export function Part() { // PUBLIC METHODS // ////////////////////////////////////////////// +/** + * Returns a part as an object suitable for inclusion in renderprops + * + * @return {object} part - A plain object representing the part + */ +Part.prototype.asProps = function () { + return { + paths: this.paths, + points: this.points, + snippets: this.snippets, + attributes: this.attributes, + height: this.height, + width: this.width, + bottomRight: this.bottomRight, + topLeft: this.topLeft, + } +} + /** * Adds an attribute in a chainable way * @@ -106,6 +124,7 @@ Part.prototype.shorthand = function () { const sa = this.context.settings?.complete ? this.context.settings?.sa || 0 : 0 const shorthand = { complete, + context: this.context, getId: this.getId, hide: this.hide, log: this.context.store.log, diff --git a/packages/core/src/pattern.mjs b/packages/core/src/pattern.mjs index 3136f0d1dd0..39bc83b2318 100644 --- a/packages/core/src/pattern.mjs +++ b/packages/core/src/pattern.mjs @@ -26,7 +26,6 @@ import { __loadPatternDefaults } from './config.mjs' export function Pattern(designConfig) { // Non-enumerable properties __addNonEnumProp(this, 'plugins', {}) - __addNonEnumProp(this, 'parts', [{}]) __addNonEnumProp(this, 'width', 0) __addNonEnumProp(this, 'height', 0) __addNonEnumProp(this, 'autoLayout', { stacks: {} }) @@ -37,17 +36,24 @@ export function Pattern(designConfig) { __addNonEnumProp(this, 'Snippet', Snippet) __addNonEnumProp(this, 'Attributes', Attributes) __addNonEnumProp(this, 'macros', {}) - __addNonEnumProp(this, '__parts', {}) + __addNonEnumProp(this, '__designParts', {}) __addNonEnumProp(this, '__inject', {}) __addNonEnumProp(this, '__dependencies', {}) __addNonEnumProp(this, '__resolvedDependencies', {}) + __addNonEnumProp(this, '__resolvedParts', []) + __addNonEnumProp(this, '__storeMethods', new Set()) + __addNonEnumProp(this, '__mutated', { + optionDistance: {}, + partDistance: {}, + partHide: {}, + partHideAll: {}, + }) __addNonEnumProp(this, '__draftOrder', []) __addNonEnumProp(this, '__hide', {}) // Enumerable properties this.designConfig = designConfig // The design configuration (unresolved) this.config = {} // Will hold the resolved pattern after calling __init() - this.stacks = {} // Drafted stacks container this.store = new Store() // Pattern-wide store this.setStores = [] // Per-set stores @@ -81,10 +87,13 @@ Pattern.prototype.addPart = function (part) { Pattern.prototype.draft = function () { this.__init() this.__runHooks('preDraft') + // Keep container for drafted parts fresh + this.parts = [] // Iterate over the provided sets of settings (typically just one) for (const set in this.settings) { this.activeSet = set + this.setStores[set] = this.__createSetStore() this.setStores[set].log.debug(`Initialized store for set ${set}`) this.__runHooks('preSetDraft') this.setStores[set].log.debug(`📐 Drafting pattern for set ${set}`) @@ -99,6 +108,7 @@ Pattern.prototype.draft = function () { // Create parts this.setStores[set].log.debug(`📦 Creating part \`${partName}\` (set ${set})`) this.parts[set][partName] = this.__createPartWithContext(partName, set) + // Handle inject/inheritance if (typeof this.__inject[partName] === 'string') { this.setStores[set].log.debug( @@ -115,11 +125,11 @@ Pattern.prototype.draft = function () { } if (this.__needs(partName, set)) { // Draft part - if (typeof this.__parts?.[partName]?.draft === 'function') { + if (typeof this.__designParts?.[partName]?.draft === 'function') { this.activePart = partName try { this.__runHooks('prePartDraft') - const result = this.__parts[partName].draft(this.parts[set][partName].shorthand()) + const result = this.__designParts[partName].draft(this.parts[set][partName].shorthand()) this.__runHooks('postPartDraft') if (typeof result === 'undefined') { this.setStores[set].log.error( @@ -129,7 +139,7 @@ Pattern.prototype.draft = function () { } catch (err) { this.setStores[set].log.error([`Unable to draft part \`${partName}\` (set ${set})`, err]) } - } else this.setStores[set].log.error(`Unable to draft pattern. Part.draft() is not callable`) + } else this.setStores[set].log.error(`Unable to draft pattern part __${partName}__. Part.draft() is not callable`) this.parts[set][partName].hidden = this.parts[set][partName].hidden === true ? true : !this.__wants(partName, set) } else { @@ -183,26 +193,23 @@ Pattern.prototype.getRenderProps = function () { warning: store.logs.warning, })) } - props.parts = {} - for (let p in this.parts) { - if (!this.parts[p].hidden) { - props.parts[p] = { - paths: this.parts[p].paths, - points: this.parts[p].points, - snippets: this.parts[p].snippets, - attributes: this.parts[p].attributes, - height: this.parts[p].height, - width: this.parts[p].width, - bottomRight: this.parts[p].bottomRight, - topLeft: this.parts[p].topLeft, - store: this.setStores[this.parts[p].set], + props.parts = [] + for (const set of this.parts) { + const setParts = {} + for (let p in set) { + if (!set[p].hidden) { + setParts[p] = { + ...set[p].asProps(), + store: this.setStores[set[p].set], + } } } + props.parts.push(setParts) } props.stacks = {} for (let s in this.stacks) { if (!this.__isStackHidden(s)) { - props.stacks[s] = this.stacks[s] + props.stacks[s] = this.stacks[s].asProps() } } @@ -308,12 +315,8 @@ Pattern.prototype.render = function () { * @return {object} this - The Pattern instance */ Pattern.prototype.use = function (plugin, data) { - const name = plugin.plugin - ? plugin.plugin.name - : plugin.name - console.log('@@@@@@@@@ in use', name, plugin, data) - if (!this.plugins?.[name]) - return (plugin.plugin && plugin.condition) + const name = getPluginName(plugin) + if (!this.plugins?.[name]) return (plugin.plugin && plugin.condition) ? this.__useIf(plugin, data) // Conditional plugin : this.__loadPlugin(plugin, data) // Regular plugin @@ -339,8 +342,8 @@ Pattern.prototype.use = function (plugin, data) { */ Pattern.prototype.__addDependency = function (name, part, dep) { this.__dependencies[name] = mergeDependencies(dep.name, this.__dependencies[name]) - if (typeof this.__parts[dep.name] === 'undefined') { - this.config = this.__addPartConfig(this.__parts[dep.name]) + if (typeof this.__designParts[dep.name] === 'undefined') { + this.config = this.__addPartConfig(this.__designParts[dep.name]) } return this @@ -356,9 +359,10 @@ Pattern.prototype.__addDependency = function (name, part, dep) { * @return {object} config - The mutated global config */ Pattern.prototype.__addPartConfig = function (part) { - if (this.__parts[part.name].resolved) return config + + if (this.__resolvedParts.includes(part.name)) return this + // Add parts, using set to keep them unique in the array - this.__parts[part.name].resolved = true this.designConfig.parts = [...new Set(this.designConfig.parts).add(part)] return this.__addPartOptions(part) @@ -442,15 +446,15 @@ Pattern.prototype.__addPartOptionalMeasurements = function (part, list=false) { * @return {Pattern} this - The Pattern instance */ Pattern.prototype.__addPartOptions = function (part) { - if (!this.config.optionDistance) this.config.optionDistance = {} if (!this.config.options) this.config.options = {} if (part.options) { for (const optionName in part.options) { - if (!this.config.optionDistance[optionName]) { - this.config.optionDistance[optionName] = part.distance - this.config.options[optionName] = part.options[optionName] + if (!this.__mutated.optionDistance[optionName]) { + this.__mutated.optionDistance[optionName] = this.__mutated.partDistance?.[part.name] || 0 + // Keep design parts immutable in the pattern or risk subtle bugs + this.config.options[optionName] = Object.freeze(part.options[optionName]) this.store.log.debug(`🔵 __${optionName}__ option loaded from \`${part.name}\``) - } else if (this.config.optionDistance[optionName] > part.distance) { + } else if (this.__mutated.optionDistance[optionName] > this.__mutated.partDistance[part.name]) { this.config.options[optionName] = part.options[optionName] this.store.log.debug(`🟣 __${optionName}__ option overwritten by \`${part.name}\``) } @@ -466,6 +470,18 @@ Pattern.prototype.__addPartOptions = function (part) { return this } +function getPluginName(plugin) { + if (Array.isArray(plugin)) { + if (plugin[0].name) return plugin[0].name + if (plugin[0].plugin.name) return plugin[0].plugin.name + } else { + if (plugin.name) return plugin.name + if (plugin.plugin.name) return plugin.plugin.name + } + + return false +} + /** * Resolves/Adds a part's configured plugins to the global config * @@ -474,44 +490,63 @@ Pattern.prototype.__addPartOptions = function (part) { * @return {Pattern} this - The Pattern instance */ Pattern.prototype.__addPartPlugins = function (part) { - if (!this.config.plugins) this.config.plugins = [] - const plugins = {} + if (!this.config.plugins) this.config.plugins = {} + const plugins = { ...this.config.plugins } if (!part.plugins) return this - for (const plugin of part.plugins) plugins[plugin.name] = plugin - if (!Array.isArray(part.plugins)) part.plugins = [part.plugins] - for (let plugin of part.plugins) { + // Side-step immutability of the part object to ensure plugins is an array + let partPlugins = part.plugins + if (!Array.isArray(partPlugins)) partPlugins = [partPlugins] + for (const plugin of partPlugins) plugins[getPluginName(plugin)] = plugin + for (let plugin of partPlugins) { + const name = getPluginName(plugin) // Handle [plugin, data] scenario if (Array.isArray(plugin)) { const pluginObj = { ...plugin[0], data: plugin[1] } plugin = pluginObj } - if (plugin.plugin) this.store.log.debug(`🔌 Resolved __${plugin.plugin.name}__ conditional plugin in \`${part.name}\``) - else this.store.log.debug(`🔌 Resolved __${plugin.name}__ plugin in \`${part.name}\``) + if (plugin.plugin) this.store.log.debug(`🔌 Resolved __${name}__ conditional plugin in \`${part.name}\``) + else this.store.log.debug(`🔌 Resolved __${name}__ plugin in \`${part.name}\``) // Do not overwrite an existing plugin with a conditional plugin unless it is also conditional if (plugin.plugin && plugin.condition) { - if (!plugins[plugin.plugin.name]) { - plugins[plugin.plugin.name] = plugin - this.store.log.info(`Plugin \`${plugin.plugin.name}\` was conditionally added.`) + if (!plugins[name]) { + plugins[name] = plugin + this.store.log.info(`Plugin \`${name}\` was conditionally added.`) } - else if (plugins[plugin.plugin.name]?.condition) { - plugins[plugin.plugin.name+'_'] = plugin - this.store.log.info(`Plugin \`${plugin.plugin.name}\` was conditionally added again. Renaming to ${plugin.plugin.name}_.`) + else if (plugins[name]?.condition) { + plugins[name+'_'] = plugin + this.store.log.info(`Plugin \`${name}\` was conditionally added again. Renaming to ${name}_.`) } else this.store.log.info( - `Plugin \`${plugin.plugin.name}\` was requested conditionally, but is already added explicitly. Not loading.` + `Plugin \`${name}\` was requested conditionally, but is already added explicitly. Not loading.` ) } else { - plugins[plugin.name] = plugin - this.store.log.info(`Plugin \`${plugin.name}\` was added.`) + plugins[name] = plugin + this.store.log.info(`Plugin \`${name}\` was added.`) } } - // Weed out doubles - this.config.plugins = [...new Set(Object.values(plugins))] + this.config.plugins = {...plugins } return this } +/** + * Creates a store for a set (of settings) + * + * @private + * @return {Store} store - A new store populated with relevant data/methods + */ +Pattern.prototype.__createSetStore = function () { + const store = new Store() + store.set('data', this.store.data) + for (const method of [...this.__storeMethods]) { + store.set(method[0], (...args) => method[1](store, ...args)) + } + + return store +} + + /** * Merges (sets of) settings with the default settings * @@ -525,7 +560,6 @@ Pattern.prototype.__applySettings = function (sets) { this.settings = [] for (const set in sets) { this.settings.push({ ...__loadPatternDefaults(), ...sets[set] }) - this.setStores.push(new Store()) } return this @@ -544,7 +578,7 @@ Pattern.prototype.__createPartWithContext = function (name, set) { const part = new Part() part.name = name part.set = set - part.stack = this.__parts[name]?.stack || name + part.stack = this.__designParts[name]?.stack || name part.context = { parts: this.parts[set], config: this.config, @@ -552,6 +586,7 @@ Pattern.prototype.__createPartWithContext = function (name, set) { store: this.setStores[set], macros: this.macros, } + if (this.settings[set]?.partClasses) { part.attr('class', this.settings[set].partClasses) } @@ -617,8 +652,8 @@ Pattern.prototype.__init = function () { .__resolveDependencies() // Resolves dependencies .__resolveDraftOrder() // Resolves draft order .__loadPlugins() // Loads plugins - .__filterOptionalMeasurements() // Removes required m's from optional list .__loadConfigData() // Makes config data available in store + .__filterOptionalMeasurements() // Removes required m's from optional list .__loadOptionDefaults() // Merges default options with user provided ones // Say hello @@ -646,8 +681,8 @@ Pattern.prototype.__isPartHidden = function (partName) { if (Array.isArray(this.settings[this.activeSet || 0].only)) { if (this.settings[this.activeSet || 0].only.includes(partName)) return false } - if (this.__parts?.[partName]?.hide) return true - if (this.__parts?.[partName]?.hideAll) return true + if (this.__designParts?.[partName]?.hide) return true + if (this.__designParts?.[partName]?.hideAll) return true return false } @@ -669,8 +704,8 @@ Pattern.prototype.__isStackHidden = function (stackName) { return true } for (const partName of parts) { - if (this.__parts?.[partName]?.hide) return true - if (this.__parts?.[partName]?.hideAll) return true + if (this.__designParts?.[partName]?.hide) return true + if (this.__designParts?.[partName]?.hideAll) return true } return false @@ -740,9 +775,7 @@ Pattern.prototype.__loadAbsoluteOptionsSet = function (set) { * @return {Pattern} this - The Pattern instance */ Pattern.prototype.__loadConfigData = function () { - if (this.designConfig.data) { - for (const i in this.settings) this.setStores[i].set('data', this.designConfig.data) - } + if (this.designConfig.data) this.store.set('data', this.designConfig.data) return this } @@ -844,7 +877,10 @@ Pattern.prototype.__loadPluginMacros = function (plugin) { */ Pattern.prototype.__loadPlugins = function () { if (!this.config.plugins) return this - for (const plugin of this.config.plugins) this.use(plugin, plugin.data) + for (const plugin in this.config.plugins) this.use( + this.config.plugins[plugin], + this.config.plugins[plugin]?.data, + ) return this } @@ -858,8 +894,11 @@ Pattern.prototype.__loadPlugins = function () { */ Pattern.prototype.__loadPluginStoreMethods = function (plugin) { if (Array.isArray(plugin.store)) { - for (const store of this.setStores) store.extend(plugin.store) - } else this.store.log.warning(`Plugin store methods should be an Array`) + for (const method of plugin.store) this.__storeMethods.add(method) + } + else this.store.log.warning(`Plugin store methods should be an Array`) + + return this } /** @@ -1143,7 +1182,7 @@ Pattern.prototype.__resolveDraftOrder = function (graph = this.__resolvedDepende }) // Don't forget about parts without dependencies - for (const part in this.__parts) { + for (const part in this.__designParts) { if (sorted.indexOf(part) === -1) sorted.push(part) } @@ -1164,49 +1203,53 @@ Pattern.prototype.__resolveDraftOrder = function (graph = this.__resolvedDepende Pattern.prototype.__resolveParts = function (count = 0, distance = 0) { if (count === 0) { for (const part of this.designConfig.parts) { - part.distance = distance - this.__parts[part.name] = part + // Keep design parts immutable in the pattern or risk subtle bugs + this.__designParts[part.name] = Object.freeze(part) } } distance++ for (const part of this.designConfig.parts) { - if (typeof part.distance === 'undefined') part.distance = distance + if (typeof this.__mutated.partDistance[part.name] === 'undefined') this.__mutated.partDistance[part.name] = distance } - for (const [name, part] of Object.entries(this.__parts)) { + for (const [name, part] of Object.entries(this.__designParts)) { // Hide when hideAll is set if (part.hideAll) part.hide = true // Inject (from) if (part.from) { if (part.hideDependencies || part.hideAll) { - part.from.hide = true - part.from.hideAll = true - part.from.distance = distance + // Don't mutate the part, keep this info in the pattern object + this.__mutated.partHide[from.name] = true + this.__mutated.partHideAll[from.name] = true + this.__mutated.partDistance = distance } - this.__parts[part.from.name] = part.from + this.__designParts[part.from.name] = part.from this.__inject[name] = part.from.name } // Simple dependency (after) if (part.after) { if (Array.isArray(part.after)) { for (const dep of part.after) { - dep.distance = distance - this.__parts[dep.name] = dep + // Don't mutate the part, keep this info in the pattern object + this.__mutated.partDistance[dep.name] = distance + this.__designParts[dep.name] = dep this.__addDependency(name, part, dep) } } else { - if (part.hideDependencies) part.after.hide = true - part.after.distance = distance - this.__parts[part.after.name] = part.after + if (part.hideDependencies) { + this.__mutated.partHide[part.after.name] = true + } + this.__mutated.partDistance[part.after.name] = distance + this.__designParts[part.after.name] = part.after this.__addDependency(name, part, part.after) } } } // Did we discover any new dependencies? - const len = Object.keys(this.__parts).length + const len = Object.keys(this.__designParts).length // If so, resolve recursively if (len > count) return this.__resolveParts(len, distance) - for (const part of Object.values(this.__parts)) this.__addPartConfig(part) + for (const part of Object.values(this.__designParts)) this.__addPartConfig(part) return this } @@ -1291,20 +1334,21 @@ Pattern.prototype.__setBase = function () { Pattern.prototype.__snappedPercentageOption = function (optionName, set) { const conf = this.config.options[optionName] const abs = conf.toAbs(this.settings[set].options[optionName], this.settings[set]) - // Handle units-specific config - if (!Array.isArray(conf.snap) && conf.snap.metric && conf.snap.imperial) - conf.snap = conf.snap[this.settings[set].units] + // Handle units-specific config - Side-step immutability for the snap conf + let snapConf = conf.snap + if (!Array.isArray(snapConf) && snapConf.metric && snapConf.imperial) + snapConf = snapConf[this.settings[set].units] // Simple steps - if (typeof conf.snap === 'number') return Math.ceil(abs / conf.snap) * conf.snap + if (typeof snapConf === 'number') return Math.ceil(abs / snapConf) * snapConf // List of snaps - if (Array.isArray(conf.snap) && conf.snap.length > 1) { - for (const snap of conf.snap + if (Array.isArray(snapConf) && snapConf.length > 1) { + for (const snap of snapConf .sort((a, b) => a - b) .map((snap, i) => { const margin = - i < conf.snap.length - 1 - ? (conf.snap[Number(i) + 1] - snap) / 2 // Look forward - : (snap - conf.snap[i - 1]) / 2 // Final snap, look backward + i < snapConf.length - 1 + ? (snapConf[Number(i) + 1] - snap) / 2 // Look forward + : (snap - snapConf[i - 1]) / 2 // Final snap, look backward return { min: snap - margin, @@ -1326,7 +1370,6 @@ Pattern.prototype.__snappedPercentageOption = function (optionName, set) { * @return {Pattern} this - The Pattern instance */ Pattern.prototype.__useIf = function (plugin) { - console.log(')_________', plugin) let load = 0 for (const set of this.settings) { if (plugin.condition(set)) load++ diff --git a/packages/core/src/stack.mjs b/packages/core/src/stack.mjs index 96e8ad27d32..32eb0b0efc2 100644 --- a/packages/core/src/stack.mjs +++ b/packages/core/src/stack.mjs @@ -26,6 +26,15 @@ Stack.prototype.addPart = function (part) { return this } +/* Returns a stack object suitbale for renderprops */ +Stack.prototype.asProps = function (part) { + return { + ...this, + parts: [...this.parts] + } +} + + /* Returns a list of parts in this stack */ Stack.prototype.getPartList = function () { return [...this.parts] diff --git a/packages/core/tests/multi.test.mjs b/packages/core/tests/multi.test.mjs index 5d82998cf02..3368ab82d09 100644 --- a/packages/core/tests/multi.test.mjs +++ b/packages/core/tests/multi.test.mjs @@ -3,8 +3,6 @@ import { Design } from '../src/index.mjs' const expect = chai.expect -if (!expect) console.log('shut up eslint REMOVE') - describe('Multisets', () => { describe('FIXME', () => { const partA = { diff --git a/packages/core/tests/part.test.mjs b/packages/core/tests/part.test.mjs index 72040cb1d31..8734da79b75 100644 --- a/packages/core/tests/part.test.mjs +++ b/packages/core/tests/part.test.mjs @@ -32,13 +32,6 @@ describe('Part', () => { }) it('Should register and run a macro', () => { - const part = { - name: 'test', - draft: ({ part, macro }) => { - macro('test', { x: 123, y: 456 }) - return part - }, - } const plugin = { name: 'test', version: '0.1-test', @@ -49,7 +42,16 @@ describe('Part', () => { }, }, } - const design = new Design({ parts: [part], plugins: [plugin] }) + const part = { + name: 'test', + draft: ({ part, Point, points, macro }) => { + points.example = new Point(12,34) + macro('test', { x: 123, y: 456 }) + return part + }, + plugins: plugin, + } + const design = new Design({ parts: [part] }) const pattern = new design() pattern.draft() expect(pattern.parts[0].test.points.macro.x).to.equal(123) @@ -92,7 +94,7 @@ describe('Part', () => { expect(part.attributes.get('foo')).to.equal('schmoo') }) - it('Should raise a warning when setting a non-Point value in points', () => { + it('Should log a warning when setting a non-Point value in points', () => { const part = { name: 'test', draft: ({ points, part }) => { @@ -103,19 +105,19 @@ describe('Part', () => { const design = new Design({ parts: [part] }) const pattern = new design() pattern.draft() - expect(pattern.stores[0].logs.warning.length).to.equal(4) - expect(pattern.stores[0].logs.warning[0]).to.equal( + expect(pattern.setStores[0].logs.warning.length).to.equal(4) + expect(pattern.setStores[0].logs.warning[0]).to.equal( '`points.a` was set with a value that is not a `Point` object' ) - expect(pattern.stores[0].logs.warning[1]).to.equal( + expect(pattern.setStores[0].logs.warning[1]).to.equal( '`points.a` was set with a `x` parameter that is not a `number`' ) - expect(pattern.stores[0].logs.warning[2]).to.equal( + expect(pattern.setStores[0].logs.warning[2]).to.equal( '`points.a` was set with a `y` parameter that is not a `number`' ) }) - it('Should raise a warning when setting a non-Snippet value in snippets', () => { + it('Should log a warning when setting a non-Snippet value in snippets', () => { const part = { name: 'test', draft: ({ snippets, part }) => { @@ -126,14 +128,14 @@ describe('Part', () => { const design = new Design({ parts: [part] }) const pattern = new design() pattern.draft() - expect(pattern.stores[0].logs.warning.length).to.equal(4) - expect(pattern.stores[0].logs.warning[0]).to.equal( + expect(pattern.setStores[0].logs.warning.length).to.equal(4) + expect(pattern.setStores[0].logs.warning[0]).to.equal( '`snippets.a` was set with a value that is not a `Snippet` object' ) - expect(pattern.stores[0].logs.warning[1]).to.equal( + expect(pattern.setStores[0].logs.warning[1]).to.equal( '`snippets.a` was set with a `def` parameter that is not a `string`' ) - expect(pattern.stores[0].logs.warning[2]).to.equal( + expect(pattern.setStores[0].logs.warning[2]).to.equal( '`snippets.a` was set with an `anchor` parameter that is not a `Point`' ) }) @@ -176,8 +178,8 @@ describe('Part', () => { // Let's also cover the branch where complete is false const pattern = new design({ complete: false} ) pattern.draft() - expect(pattern.stores[0].logs.warning.length).to.equal(1) - expect(pattern.stores[0].logs.warning[0]).to.equal( + expect(pattern.setStores[0].logs.warning.length).to.equal(1) + expect(pattern.setStores[0].logs.warning[0]).to.equal( 'Calling `units(value)` but `value` is not a number (`string`)' ) }) @@ -291,8 +293,8 @@ describe('Part', () => { const design = new Design({ parts: [part] }) const pattern = new design() pattern.draft() - expect(pattern.stores[0].logs.warning.length).to.equal(1) - expect(pattern.stores[0].logs.warning[0]).to.equal('Tried to access `options.test` but it is `undefined`') + expect(pattern.setStores[0].logs.warning.length).to.equal(1) + expect(pattern.setStores[0].logs.warning[0]).to.equal('Tried to access `options.test` but it is `undefined`') }) it('Accessing unknown absoluteOption should log a warning', () => { @@ -305,8 +307,8 @@ describe('Part', () => { const design = new Design({ parts: [part] }) const pattern = new design() pattern.draft() - expect(pattern.stores[0].logs.warning.length).to.equal(1) - expect(pattern.stores[0].logs.warning[0]).to.equal('Tried to access `absoluteOptions.test` but it is `undefined`') + expect(pattern.setStores[0].logs.warning.length).to.equal(1) + expect(pattern.setStores[0].logs.warning[0]).to.equal('Tried to access `absoluteOptions.test` but it is `undefined`') }) it('Injecting a part should contain all data', () => { diff --git a/packages/core/tests/path.test.mjs b/packages/core/tests/path.test.mjs index a5ad05bf49d..40cc6bf95a2 100644 --- a/packages/core/tests/path.test.mjs +++ b/packages/core/tests/path.test.mjs @@ -77,8 +77,8 @@ describe('Path', () => { const design = new Design({ parts: [part] }) const pattern = new design() pattern.draft() - expect(pattern.stores[0].logs.warning.length).to.equal(2) - expect(pattern.stores[0].logs.warning[0]).to.equal('Called `Path.smurve(cp2, to)` but `to` is not a `Point` object') + expect(pattern.setStores[0].logs.warning.length).to.equal(2) + expect(pattern.setStores[0].logs.warning[0]).to.equal('Called `Path.smurve(cp2, to)` but `to` is not a `Point` object') }) it('Should log a warning when passing a non-Point to smurve_()', () => { @@ -93,8 +93,8 @@ describe('Path', () => { const design = new Design({ parts: [part] }) const pattern = new design() pattern.draft() - expect(pattern.stores[0].logs.warning.length).to.equal(1) - expect(pattern.stores[0].logs.warning[0]).to.equal('Called `Path.smurve_(to)` but `to` is not a `Point` object') + expect(pattern.setStores[0].logs.warning.length).to.equal(1) + expect(pattern.setStores[0].logs.warning[0]).to.equal('Called `Path.smurve_(to)` but `to` is not a `Point` object') }) it('Should log a warning when passing a non-Path to the paths proxy', () => { @@ -109,9 +109,9 @@ describe('Path', () => { const design = new Design({ parts: [part] }) const pattern = new design() pattern.draft() - expect(pattern.stores[0].logs.warning.length).to.equal(2) - expect(pattern.stores[0].logs.warning[0]).to.equal('`paths.test` was set with a value that is not a `Path` object') - expect(pattern.stores[0].logs.warning[1]).to.equal('Could not set `name` property on `paths.test`') + expect(pattern.setStores[0].logs.warning.length).to.equal(2) + expect(pattern.setStores[0].logs.warning[0]).to.equal('`paths.test` was set with a value that is not a `Path` object') + expect(pattern.setStores[0].logs.warning[1]).to.equal('Could not set `name` property on `paths.test`') }) it('Should offset a line', () => { @@ -1074,8 +1074,8 @@ describe('Path', () => { const design = new Design({ parts: [part] }) const pattern = new design() pattern.draft() - expect(pattern.stores[0].logs.error.length).to.equal(2) - expect(pattern.stores[0].logs.error[0]).to.equal( + expect(pattern.setStores[0].logs.error.length).to.equal(2) + expect(pattern.setStores[0].logs.error[0]).to.equal( 'Called `Path.offset(distance)` but `distance` is not a number' ) }) @@ -1092,8 +1092,8 @@ describe('Path', () => { const design = new Design({ parts: [part] }) const pattern = new design() pattern.draft() - expect(pattern.stores[0].logs.error.length).to.equal(2) - expect(pattern.stores[0].logs.error[0]).to.equal( + expect(pattern.setStores[0].logs.error.length).to.equal(2) + expect(pattern.setStores[0].logs.error[0]).to.equal( 'Called `Path.join(that)` but `that` is not a `Path` object' ) }) @@ -1141,7 +1141,7 @@ describe('Path', () => { const design = new Design({ parts: [part] }) const pattern = new design() pattern.draft() - expect(pattern.stores[0].logs.error[0]).to.equal( + expect(pattern.setStores[0].logs.error[0]).to.equal( 'Called `Path.shiftFractionAlong(fraction)` but `fraction` is not a number' ) }) @@ -1157,7 +1157,7 @@ describe('Path', () => { const design = new Design({ parts: [part] }) const pattern = new design() pattern.draft() - expect(pattern.stores[0].logs.error[0]).to.equal( + expect(pattern.setStores[0].logs.error[0]).to.equal( 'Called `Path.split(point)` but `point` is not a `Point` object' ) }) diff --git a/packages/core/tests/pattern-init.test.mjs b/packages/core/tests/pattern-init.test.mjs index 68a0e31dd5a..425da3d6c8e 100644 --- a/packages/core/tests/pattern-init.test.mjs +++ b/packages/core/tests/pattern-init.test.mjs @@ -5,7 +5,6 @@ const expect = chai.expect describe('Pattern', () => { describe('Pattern.constructor()', () => { - /* it('Pattern constructor should return pattern object', () => { const Pattern = new Design() @@ -20,7 +19,7 @@ describe('Pattern', () => { expect(Array.isArray(pattern.setStores)).to.equal(true) expect(typeof pattern.store).to.equal('object') expect(typeof pattern.config).to.equal('object') - expect(Object.keys(pattern).length).to.equal(6) + expect(Object.keys(pattern).length).to.equal(5) }) it('Pattern constructor should add non-enumerable properties', () => { @@ -34,7 +33,7 @@ describe('Pattern', () => { expect(typeof pattern.Snippet).to.equal('function') expect(typeof pattern.Attributes).to.equal('function') expect(typeof pattern.macros).to.equal('object') - expect(typeof pattern.__parts).to.equal('object') + expect(typeof pattern.__designParts).to.equal('object') expect(typeof pattern.__inject).to.equal('object') expect(typeof pattern.__dependencies).to.equal('object') expect(typeof pattern.__resolvedDependencies).to.equal('object') @@ -75,7 +74,7 @@ describe('Pattern', () => { options: { optA: { pct: 40, min: 20, max: 80 }, }, - draft: () => {}, + draft: ({ part }) => part, } const partB = { name: 'test.partB', @@ -93,7 +92,7 @@ describe('Pattern', () => { options: { optB: { deg: 40, min: 20, max: 80 }, }, - draft: () => {}, + draft: ({ part }) => part, } const partC = { name: 'test.partC', @@ -103,7 +102,7 @@ describe('Pattern', () => { options: { optC: { pct: 20, min: 10, max: 30 }, }, - draft: () => {}, + draft: ({ part }) => part, } const Pattern = new Design({ @@ -114,7 +113,7 @@ describe('Pattern', () => { parts: [partC], }) const pattern = new Pattern() - pattern.__init() + pattern.draft() it('Pattern.__init() should resolve all measurements', () => { expect( @@ -152,7 +151,7 @@ describe('Pattern', () => { }) it('Pattern.__init() should resolve plugins', () => { - expect(pattern.config.plugins.length).to.equal(1) + expect(Object.keys(pattern.config.plugins).length).to.equal(1) }) it('Pattern.__init() should set config data in the store', () => { @@ -497,7 +496,7 @@ describe('Pattern', () => { } const design = new Design({ parts: [part] }) const pattern = new design() - pattern.__init() + pattern.draft() expect(pattern.hooks.preRender.length).to.equal(1) }) @@ -573,9 +572,10 @@ describe('Pattern', () => { const pattern = new design() expect(pattern.hooks.preRender.length).to.equal(0) }) + it('Pattern.__init() should load multiple conditional plugins', () => { - const plugin = { - name: 'example', + const plugin1 = { + name: 'example1', version: 1, hooks: { preRender: function (svg) { @@ -583,22 +583,30 @@ describe('Pattern', () => { }, }, } + const plugin2 = { + name: 'example2', + version: 2, + hooks: { + preRender: function (svg) { + svg.attributes.add('freesewing:plugin-example', 2) + }, + }, + } const condition1 = () => true const condition2 = () => false const part = { name: 'test.part', plugins: [ - { plugin, condition: condition1 }, - { plugin, condition: condition2 }, + { plugin: plugin1, condition: condition1 }, + { plugin: plugin2, condition: condition2 }, ], draft: (part) => part, } const design = new Design({ parts: [ part ] }) const pattern = new design() - pattern.__init() + pattern.draft() expect(pattern.hooks.preRender.length).to.equal(1) }) -*/ it('Load conditional plugins that are also passing data', () => { const plugin1 = { @@ -638,17 +646,13 @@ describe('Pattern', () => { draft: ({ part }) => part } const design = new Design({ - parts: [ part1 ] + parts: [ part1, part2 ] }) const pattern = new design() pattern.__init() - //console.log(pattern.store.logs) - console.log(pattern.config) - //console.log(pattern.hooks) expect(pattern.hooks.preRender.length).to.equal(2) }) - /* it('Pattern.__init() should register a hook via on', () => { const Pattern = new Design() const pattern = new Pattern() @@ -704,35 +708,44 @@ describe('Pattern', () => { }) it('Should check whether created parts get the pattern context', () => { + let partContext const part = { name: 'test', - draft: ({ part }) => part, + draft: ({ Point, paths, Path, part, context }) => { + paths.test = new Path() + .move(new Point(0,0)) + .line(new Point(100,0)) + partContext = context + + return part + } } - const Pattern = new Design({ parts: [part] }) + const Pattern = new Design({ parts: [part], data: { name: 'test', version: '1' }}) const pattern = new Pattern() pattern.draft() - const context = pattern.parts[0].test.context - expect(typeof context).to.equal('object') - expect(typeof context.parts).to.equal('object') - expect(typeof context.config).to.equal('object') - expect(typeof context.config.options).to.equal('object') - expect(typeof pattern.parts[0].test.context.config.data).to.equal('object') - expect(Array.isArray(context.config.measurements)).to.equal(true) - expect(Array.isArray(context.config.optionalMeasurements)).to.equal(true) - expect(Array.isArray(context.config.parts)).to.equal(true) - expect(Array.isArray(context.config.plugins)).to.equal(true) - expect(context.settings).to.equal(pattern.settings[0]) - expect(typeof context.store).to.equal('object') - expect(typeof context.store.log).to.equal('object') - expect(typeof context.store.log.debug).to.equal('function') - expect(typeof context.store.log.info).to.equal('function') - expect(typeof context.store.log.warning).to.equal('function') - expect(typeof context.store.log.error).to.equal('function') - expect(typeof context.store.logs).to.equal('object') - expect(Array.isArray(context.store.logs.debug)).to.equal(true) - expect(Array.isArray(context.store.logs.info)).to.equal(true) - expect(Array.isArray(context.store.logs.warning)).to.equal(true) - expect(Array.isArray(context.store.logs.error)).to.equal(true) + expect(typeof partContext).to.equal('object') + expect(typeof partContext.parts).to.equal('object') + expect(typeof partContext.config).to.equal('object') + expect(typeof partContext.config.options).to.equal('object') + expect(typeof partContext.store.data).to.equal('object') + expect(partContext.store.data.name).to.equal('test') + expect(partContext.store.get('data.name')).to.equal('test') + expect(Array.isArray(partContext.config.measurements)).to.equal(true) + expect(Array.isArray(partContext.config.optionalMeasurements)).to.equal(true) + expect(typeof partContext.config.plugins).to.equal('object') + expect(typeof partContext.parts).to.equal('object') + expect(partContext.settings).to.equal(pattern.settings[0]) + expect(typeof partContext.store).to.equal('object') + expect(typeof partContext.store.log).to.equal('object') + expect(typeof partContext.store.log.debug).to.equal('function') + expect(typeof partContext.store.log.info).to.equal('function') + expect(typeof partContext.store.log.warning).to.equal('function') + expect(typeof partContext.store.log.error).to.equal('function') + expect(typeof partContext.store.logs).to.equal('object') + expect(Array.isArray(partContext.store.logs.debug)).to.equal(true) + expect(Array.isArray(partContext.store.logs.info)).to.equal(true) + expect(Array.isArray(partContext.store.logs.warning)).to.equal(true) + expect(Array.isArray(partContext.store.logs.error)).to.equal(true) }) }) @@ -800,6 +813,5 @@ describe('Pattern', () => { const pattern = new Pattern() expect(() => pattern.__init()).to.throw() }) - */ }) }) diff --git a/packages/core/tests/pattern-other.test.mjs b/packages/core/tests/pattern-other.test.mjs index e2cbe8e12da..fc4f311319d 100644 --- a/packages/core/tests/pattern-other.test.mjs +++ b/packages/core/tests/pattern-other.test.mjs @@ -10,14 +10,14 @@ describe('Pattern', () => { const design = new Design() const pattern = new design() pattern.addPart(part) - expect(pattern.stores[0].logs.error.length).to.equal(1) - expect(pattern.stores[0].logs.error[0]).to.equal('Part must have a name') + expect(pattern.store.logs.error.length).to.equal(1) + expect(pattern.store.logs.error[0]).to.equal('Part must have a name') }) it('Should log an error when a part does not have a draft method', () => { const from = { name: 'test', - draft: ({ points, part }) => { + noDraft: ({ points, part }) => { points.test = false return part } @@ -32,9 +32,8 @@ describe('Pattern', () => { const design = new Design({ parts: [ to ]}) const pattern = new design() pattern.draft() - expect(pattern.stores[0].logs.error.length).to.equal(2) - expect(pattern.stores[0].logs.error[0][0]).to.equal('Unable to draft part `test` (set 0)') - expect(pattern.stores[0].logs.error[1][0]).to.equal('Could not inject part `test` into part `testTo`') + expect(pattern.setStores[0].logs.error.length).to.equal(1) + expect(pattern.setStores[0].logs.error[0]).to.equal('Unable to draft pattern part __test__. Part.draft() is not callable') }) it('Not returning the part from the draft method should log an error', () => { @@ -45,8 +44,8 @@ describe('Pattern', () => { const design = new Design({ parts: [ test ]}) const pattern = new design() pattern.draft() - expect(pattern.stores[0].logs.error.length).to.equal(1) - expect(pattern.stores[0].logs.error[0]).to.equal('Result of drafting part test was undefined. Did you forget to return the part?') + expect(pattern.setStores[0].logs.error.length).to.equal(1) + expect(pattern.setStores[0].logs.error[0]).to.equal('Result of drafting part test was undefined. Did you forget to return the part?') }) it('Should skip unneeded parts', () => { @@ -57,8 +56,8 @@ describe('Pattern', () => { const design = new Design({ parts: [ test ]}) const pattern = new design({ only: ['you'] }) pattern.draft() - expect(pattern.stores[0].logs.debug.length).to.equal(3) - expect(pattern.stores[0].logs.debug[2]).to.equal('Part `test` is not needed. Skipping draft and setting hidden to `true`') + expect(pattern.setStores[0].logs.debug.length).to.equal(4) + expect(pattern.setStores[0].logs.debug[3]).to.equal('Part `test` is not needed. Skipping draft and setting hidden to `true`') }) it('Should return the initialized config', () => { @@ -69,7 +68,8 @@ describe('Pattern', () => { const design = new Design({ parts: [ test ]}) const pattern = new design({ only: ['you'] }) const config = pattern.getConfig() - expect(config.parts[0]).to.equal(test) + expect(config.draftOrder.length).to.equal(1) + expect(config.draftOrder[0]).to.equal('test') }) it('Should skip a plugin that is loaded twice', () => { @@ -84,16 +84,16 @@ describe('Pattern', () => { pattern.use(plugin) pattern.use({ plugin }) pattern.use({ plugin }) - expect(pattern.stores[0].logs.info[1]).to.equal("Plugin `test` was requested, but it's already loaded. Skipping.") - expect(pattern.stores[0].logs.info[3]).to.equal("Plugin `test` was requested, but it's already loaded. Skipping.") + expect(Object.keys(pattern.plugins).length).to.equal(1) + expect(Object.keys(pattern.plugins)[0]).to.equal('test') }) it('Should log an error of added parts do not have a draft method', () => { const design = new Design() const pattern = new design() pattern.addPart({}) - expect(pattern.stores[0].logs.error.length).to.equal(1) - expect(pattern.stores[0].logs.error[0]).to.equal('Part must have a draft() method') + expect(pattern.store.logs.error.length).to.equal(1) + expect(pattern.store.logs.error[0]).to.equal('Part must have a draft() method') }) it('Parts in only are never hidden', () => { @@ -138,31 +138,6 @@ describe('Pattern', () => { expect(pattern.__isStackHidden('test')).to.equal(false) }) - it('Stacks with hidden dependencies should set hidden', () => { - const part1 = { - name: 'test1', - draft: ({ points, Point, paths, Path, part }) => { - points.test = new Point(3, 3) - - return part - }, - } - const part2 = { - name: 'test2', - from: part1, - hideDependencies: true, - draft: ({ points, Point, paths, Path, part }) => { - points.test = new Point(3, 3) - - return part - }, - } - const design = new Design({ parts: [part2] }) - const pattern = new design() - const config = pattern.getConfig() - expect(config.parts[1].hideAll).to.equal(true) - }) - it('Drafts with errors should not get packed', () => { const part= { name: 'test', @@ -176,8 +151,8 @@ describe('Pattern', () => { const design = new Design({ parts: [part] }) const pattern = new design() pattern.draft().render() - expect(pattern.stores[0].logs.error.length).to.equal(1) - expect(pattern.stores[0].logs.error[0][0]).to.equal('Unable to draft part `test` (set 0)') + expect(pattern.setStores[0].logs.error.length).to.equal(1) + expect(pattern.setStores[0].logs.error[0][0]).to.equal('Unable to draft part `test` (set 0)') }) it('Handle layout object', () => { diff --git a/packages/core/tests/pattern-sample.test.mjs b/packages/core/tests/pattern-sample.test.mjs index b79f0384d82..6c1daba16c4 100644 --- a/packages/core/tests/pattern-sample.test.mjs +++ b/packages/core/tests/pattern-sample.test.mjs @@ -30,7 +30,7 @@ describe('Pattern', () => { } }) pattern.sample() - expect(pattern.stores.length).to.equal(10) + expect(pattern.setStores.length).to.equal(10) expect(pattern.settings.length).to.equal(10) expect(pattern.parts[9].test.paths.test.ops[1].to.y).to.equal(320) }) @@ -59,7 +59,7 @@ describe('Pattern', () => { } }) pattern.sample() - expect(pattern.stores.length).to.equal(10) + expect(pattern.setStores.length).to.equal(10) expect(pattern.settings.length).to.equal(10) expect(round(pattern.parts[9].test.paths.test.ops[1].to.y)).to.equal(22) }) @@ -88,7 +88,7 @@ describe('Pattern', () => { } }) pattern.sample() - expect(pattern.stores.length).to.equal(10) + expect(pattern.setStores.length).to.equal(10) expect(pattern.settings.length).to.equal(10) expect(pattern.parts[9].test.paths.test.ops[1].to.y).to.equal(400) }) @@ -117,7 +117,7 @@ describe('Pattern', () => { } }) pattern.sample() - expect(pattern.stores.length).to.equal(10) + expect(pattern.setStores.length).to.equal(10) expect(pattern.settings.length).to.equal(10) expect(pattern.parts[9].test.paths.test.ops[1].to.y).to.equal(216) }) @@ -146,8 +146,8 @@ describe('Pattern', () => { } }) pattern.sample() - expect(pattern.stores[0].logs.error.length).to.equal(1) - expect(pattern.stores[0].logs.error[0]).to.equal("Cannot sample measurement `head` because it's `undefined`") + expect(pattern.store.logs.error.length).to.equal(1) + expect(pattern.store.logs.error[0]).to.equal("Cannot sample measurement `head` because it's `undefined`") }) it('Should sample models', () => { @@ -180,7 +180,7 @@ describe('Pattern', () => { } }) pattern.sample() - expect(pattern.stores.length).to.equal(4) + expect(pattern.setStores.length).to.equal(4) expect(pattern.settings.length).to.equal(4) expect(pattern.parts[3].test.paths.test.ops[1].to.y).to.equal(200) }) @@ -217,7 +217,6 @@ describe('Pattern', () => { } }) pattern.sample(); - console.log(pattern.parts) expect(pattern.parts.front.paths.line_1.ops[1].to.x).to.equal(100); expect(pattern.parts.front.paths.line_2.ops[1].to.x).to.equal(200); expect(pattern.parts.front.paths.line_3.ops[1].to.x).to.equal(300); diff --git a/packages/core/tests/snap.test.mjs b/packages/core/tests/snap.test.mjs index c0a6f2bbe6e..c7c92b9ce0f 100644 --- a/packages/core/tests/snap.test.mjs +++ b/packages/core/tests/snap.test.mjs @@ -7,20 +7,28 @@ const measurements = { head: 400 } const toAbs = (val, { measurements }) => measurements.head * val describe('Snapped options', () => { - it('Should snap a percentage options to equal steps', () => { + it('Should snap a percentage option to equal steps', () => { + let abs const part = { name: 'test', measurements: ['head'], options: { test: { pct: 30, min: 0, max: 100, snap: 12, toAbs }, }, + draft: ({ part, absoluteOptions }) => { + abs = absoluteOptions + return part + } } - const design = new Design({ parts: [part] }) - const patternA = new design({ options: { test: 0.13 }, measurements }).draft() - const patternB = new design({ options: { test: 0.27 }, measurements }).draft() - expect(patternA.settings[0].absoluteOptions.test).to.equal(60) - expect(patternB.settings[0].absoluteOptions.test).to.equal(108) + const design = new Design({ parts: [ part] }) + new design({ options: { test: 0.13 }, measurements, idPrefix: 'A' }).draft() + expect(abs.test).to.equal(60) + new design({ options: { test: 0.27 }, measurements, idPrefix: 'B' }).draft() + expect(abs.test).to.equal(108) + new design({ options: { test: 0.71 }, measurements, idPrefix: 'C' }).draft() + expect(abs.test).to.equal(288) }) + it('Should snap a percentage options to the Fibonacci sequence', () => { const part = { name: 'test', diff --git a/packages/core/tests/svg.test.mjs b/packages/core/tests/svg.test.mjs index c5237210579..e5b48579d5b 100644 --- a/packages/core/tests/svg.test.mjs +++ b/packages/core/tests/svg.test.mjs @@ -266,7 +266,7 @@ describe('Svg', () => { pattern.on('postRender', (svg) => { svg.svg = 'test' }) - expect(pattern.render()).to.equal('test') + expect(pattern.draft().render()).to.equal('test') }) it('Should tab in and out', () => {