diff --git a/packages/core/src/design.js b/packages/core/src/design.js index a48ade57f79..62b4b5dc04b 100644 --- a/packages/core/src/design.js +++ b/packages/core/src/design.js @@ -1,29 +1,38 @@ import Pattern from './pattern' -// Default hide method for options -const hide = () => false - +/* + * The Design constructor. Returns a Pattern constructor + * So it's sort of a super-constructor + */ export default function Design(config, plugins = false, conditionalPlugins = false) { - // Add default hide() method to config.options - for (const option in config.options) { - if (typeof config.options[option] === 'object') { - config.options[option] = { - hide, - ...config.options[option], - } - } - } + + // Ensure all options have a hide() method + config.options = optionsWithHide(config.options) + + // A place to store deprecation and other warnings before we even have a pattern instantiated + config.warnings = [] + + /* + * The newer way to initalize a design is to pass one single parameter + * The old way passed multiple parameters. + * So let's figure out which is which and be backwards compatible + * + * This mitigation should be removed in v3 when we drop support for the legacy way + */ + config = migrateConfig(config, plugins, conditionalPlugins) const pattern = function (settings) { Pattern.call(this, config) + // Load plugins - if (Array.isArray(plugins)) for (let plugin of plugins) this.use(plugin) - else if (plugins) this.use(plugins) + if (Array.isArray(config.plugins)) for (const plugin of config.plugins) this.use(plugin) + else if (config.plugins) this.use(config.plugins) + // Load conditional plugins - if (Array.isArray(conditionalPlugins)) - for (let plugin of conditionalPlugins) this.useIf(plugin, settings) - else if (conditionalPlugins.plugin && conditionalPlugins.condition) - this.useIf(conditionalPlugins, settings) + if (Array.isArray(config.conditionalPlugins)) + for (const plugin of config.conditionalPlugins) this.useIf(plugin, settings) + else if (config.conditionalPlugins.plugin && config.conditionalPlugins.condition) + this.useIf(config.conditionalPlugins, settings) this.apply(settings) @@ -39,3 +48,59 @@ export default function Design(config, plugins = false, conditionalPlugins = fal return pattern } + +/* + * Helper method to handle the legacy way of passing configuration + * to the design constructor + */ +const migrateConfig = (config, plugins, conditionalPlugins) => { + + // Migrate plugins + if (plugins && config.plugins) config.warnings.push( + 'Passing plugins to the Design constructor both as a second parameter and in the config is unsupported', + 'Ignoring plugins passed as parameter. Only config.plugins will be used.' + ) + else if (plugins && !config.plugins) { + config.plugins = plugins + config.warnings.push( + 'Passing a plugins parameter to the Design constructure is deprecated', + 'Please store them in the `plugins` key of the config object that is the first parameter' + ) + } else if (!config.plugins) config.plugins = [] + + // Migrate conditional plugins + if (conditionalPlugins && config.conditionalPlugins) config.warnings.push( + 'Passing conditionalPlugins to the Design constructor both as a third parameter and in the config is unsupported.', + 'Ignoring conditionalPlugins passes as parameter. Only config.conditionalPlugins will be used.', + ) + else if (conditionalPlugins && !config.conditionalPlugins) { + config.conditionalPlugins = conditionalPlugins + config.warnings.push( + 'Passing a conditionalPlugins parameter to the Design constructure is deprecated.', + 'Please store them in the `conditionalPlugins` key of the config object that is the first parameter' + ) + } + else if (!config.conditionalPlugins) config.conditionalPlugins = [] + + return config +} + +/* + * A default hide() method for options that lack it + * Since this will always return false, the option will never be hidden + */ +const hide = () => false // The default hide() method + +/* + * Helper method to add the default hide() method to options who lack one + */ +const optionsWithHide = options => { + if (options) { + for (const option in options) { + if (typeof options[option] === 'object') options[option] = { hide, ...options[option] } + } + } + + return options +} + diff --git a/packages/core/src/pattern.js b/packages/core/src/pattern.js index 2b16ea2ad31..1c932638234 100644 --- a/packages/core/src/pattern.js +++ b/packages/core/src/pattern.js @@ -11,7 +11,8 @@ import Attributes from './attributes' import pkg from '../package.json' export default function Pattern(config = { options: {} }) { - // Default settings + + // Apply default settings this.settings = { complete: true, idPrefix: 'fs-', @@ -25,15 +26,16 @@ export default function Pattern(config = { options: {} }) { absoluteOptions: {}, } - // Events store and raise methods + // Object to hold events this.events = { info: [], warning: [], error: [], debug: [], } + + // Raise methods - Make events and settings avialable in them const events = this.events - // Make settings available in the raise.debug method const settings = this.settings this.raise = { info: function (data) { @@ -50,10 +52,13 @@ export default function Pattern(config = { options: {} }) { if (settings.debug) events.debug.push(data) }, } + + // Say hi this.raise.info( `New \`@freesewing/${config.name}:${config.version}\` pattern using \`@freesewing/core:${pkg.version}\`` ) + // More things that go in a pattern this.config = config // Pattern configuration this.width = 0 // Will be set after render this.height = 0 // Will be set after render diff --git a/packages/core/tests/design.test.js b/packages/core/tests/design.test.js index e367c5972ab..fc244c553c6 100644 --- a/packages/core/tests/design.test.js +++ b/packages/core/tests/design.test.js @@ -21,7 +21,7 @@ it("Design constructor should return pattern constructor", () => { expect(pattern.settings.options.percentage).to.equal(0.3); }); -it("Design constructor should load single plugin", () => { +it("Design constructor should load single plugin (legacy)", () => { let plugin = { name: "example", version: 1, @@ -37,7 +37,23 @@ it("Design constructor should load single plugin", () => { expect(pattern.hooks.preRender.length).to.equal(1); }); -it("Design constructor should load array of plugins", () => { +it("Design constructor should load single plugin (2022)", () => { + let plugin = { + name: "example", + version: 1, + hooks: { + preRender: function(svg, attributes) { + svg.attributes.add("freesewing:plugin-example", version); + } + } + }; + + let design = new freesewing.Design({plugins: plugin}); + let pattern = new design(); + expect(pattern.hooks.preRender.length).to.equal(1); +}); + +it("Design constructor should load array of plugins (legacy)", () => { let plugin1 = { name: "example1", version: 1, @@ -62,7 +78,32 @@ it("Design constructor should load array of plugins", () => { expect(pattern.hooks.preRender.length).to.equal(2); }); -it("Design constructor should load conditional plugin", () => { +it("Design constructor should load array of plugins (2022)", () => { + let plugin1 = { + name: "example1", + version: 1, + hooks: { + preRender: function(svg, attributes) { + svg.attributes.add("freesewing:plugin-example1", version); + } + } + }; + let plugin2 = { + name: "example2", + version: 2, + hooks: { + preRender: function(svg, attributes) { + svg.attributes.add("freesewing:plugin-example2", version); + } + } + }; + + let design = new freesewing.Design( { plugins: [plugin1, plugin2] }); + let pattern = new design(); + expect(pattern.hooks.preRender.length).to.equal(2); +}); + +it("Design constructor should load conditional plugin (legacy)", () => { const plugin = { name: "example", version: 1, @@ -78,7 +119,23 @@ it("Design constructor should load conditional plugin", () => { expect(pattern.hooks.preRender.length).to.equal(1); }); -it("Design constructor should not load conditional plugin", () => { +it("Design constructor should load conditional plugin (2022)", () => { + const plugin = { + name: "example", + version: 1, + hooks: { + preRender: function(svg, attributes) { + svg.attributes.add("freesewing:plugin-example", version); + } + } + }; + const condition = () => true + const design = new freesewing.Design({ conditionalPlugins: { plugin, condition } }); + const pattern = new design(); + expect(pattern.hooks.preRender.length).to.equal(1); +}); + +it("Design constructor should not load conditional plugin (legacy)", () => { const plugin = { name: "example", version: 1, @@ -94,7 +151,23 @@ it("Design constructor should not load conditional plugin", () => { expect(pattern.hooks.preRender.length).to.equal(0); }); -it("Design constructor should load multiple conditional plugins", () => { +it("Design constructor should not load conditional plugin (2022)", () => { + const plugin = { + name: "example", + version: 1, + hooks: { + preRender: function(svg, attributes) { + svg.attributes.add("freesewing:plugin-example", version); + } + } + }; + const condition = () => false + const design = new freesewing.Design({ conditionalPlugins: { plugin, condition } }); + const pattern = new design(); + expect(pattern.hooks.preRender.length).to.equal(0); +}); + +it("Design constructor should load multiple conditional plugins (legacy)", () => { const plugin = { name: "example", version: 1, @@ -114,6 +187,26 @@ it("Design constructor should load multiple conditional plugins", () => { expect(pattern.hooks.preRender.length).to.equal(1); }); +it("Design constructor should load multiple conditional plugins (2022)", () => { + const plugin = { + name: "example", + version: 1, + hooks: { + preRender: function(svg, attributes) { + svg.attributes.add("freesewing:plugin-example", version); + } + } + }; + const condition1 = () => true + const condition2 = () => false + const design = new freesewing.Design({ conditionalPlugins: [ + { plugin, condition: condition1 }, + { plugin, condition: condition2 }, + ]}); + const pattern = new design(); + expect(pattern.hooks.preRender.length).to.equal(1); +}); + it("Design constructor should construct basic part order", () => { let design = new freesewing.Design({ dependencies: { step4: "step3" }, @@ -209,7 +302,7 @@ it("Design constructor should handle Simon", () => { let pattern = new design(); }); -it("Design constructor should add default hide() method to options", () => { +it("Pattern constructor should add default hide() method to options", () => { const design = new freesewing.Design({ foo: "bar", options: { @@ -233,3 +326,19 @@ it("Design constructor should add default hide() method to options", () => { expect(pattern.config.options.degree.hide()).to.be.false expect(pattern.config.options.withHide.hide(pattern.settings)).to.be.true }) + +it("Should warn when passing plugins both as parameter and in the config", () => { + const design = new freesewing.Design({plugins: [{}]}, {}); + expect(design.config.warnings.length).to.equal(2) + expect(design.config.warnings[0]).to.equal('Passing plugins to the Design constructor both as a second parameter and in the config is unsupported') + expect(design.config.warnings[1]).to.equal('Ignoring plugins passed as parameter. Only config.plugins will be used.') +}) + +it("Should warn when passing conditionalPlugins both as parameter and in the config", () => { + const design = new freesewing.Design({conditionalPlugins: [{}]}, false, {}); + expect(design.config.warnings.length).to.equal(2) + expect(design.config.warnings[0]).to.equal('Passing conditionalPlugins to the Design constructor both as a third parameter and in the config is unsupported.') + expect(design.config.warnings[1]).to.equal('Ignoring conditionalPlugins passes as parameter. Only config.conditionalPlugins will be used.') +}) + + diff --git a/packages/core/tests/pattern.test.js b/packages/core/tests/pattern.test.js index 0205b061119..a2611e13640 100644 --- a/packages/core/tests/pattern.test.js +++ b/packages/core/tests/pattern.test.js @@ -19,6 +19,7 @@ it("Pattern constructor should initialize object", () => { expect(pattern.settings.options.percentage).to.equal(0.3); }); + it("Should load percentage options", () => { let pattern = new freesewing.Pattern({ options: {