diff --git a/packages/core/src/config.mjs b/packages/core/src/config.mjs index 9b775eb8e6c..bb599ccd49c 100644 --- a/packages/core/src/config.mjs +++ b/packages/core/src/config.mjs @@ -34,4 +34,5 @@ export const __loadPatternDefaults = () => ({ debug: false, options: {}, absoluteOptions: {}, + measurements: {} }) diff --git a/packages/core/src/part.mjs b/packages/core/src/part.mjs index 838a609b2c8..aba624a33a2 100644 --- a/packages/core/src/part.mjs +++ b/packages/core/src/part.mjs @@ -95,7 +95,6 @@ Part.prototype.setHidden = function (hidden = false) { return this } -/** Returns an object with shorthand access for pattern design */ /** * Returns an object that will be passed to draft method to be destructured * @@ -152,13 +151,13 @@ Part.prototype.shorthand = function () { shorthand.Snippet.prototype = Object.create(Snippet.prototype) // Proxy points, paths, snippets, measurements, options, and absoluteOptions - shorthand.points = new Proxy(this.points || {}, pointsProxy(self.points, self.context.store.log)) - shorthand.paths = new Proxy(this.paths || {}, pathsProxy(self.paths, self.context.store.log)) + shorthand.points = new Proxy(this.points, pointsProxy(self.points, self.context.store.log)) + shorthand.paths = new Proxy(this.paths, pathsProxy(self.paths, self.context.store.log)) shorthand.snippets = new Proxy( - this.snippets || {}, + this.snippets, snippetsProxy(self.snippets, self.context.store.log) ) - shorthand.measurements = new Proxy(this.context.settings.measurements || {}, { + shorthand.measurements = new Proxy(this.context.settings.measurements, { get: function (measurements, name) { if (typeof measurements[name] === 'undefined') self.context.store.log.warning( @@ -168,7 +167,7 @@ Part.prototype.shorthand = function () { }, set: (measurements, name, value) => (self.context.settings.measurements[name] = value), }) - shorthand.options = new Proxy(this.context.settings.options || {}, { + shorthand.options = new Proxy(this.context.settings.options, { get: function (options, name) { if (typeof options[name] === 'undefined') self.context.store.log.warning( @@ -178,7 +177,7 @@ Part.prototype.shorthand = function () { }, set: (options, name, value) => (self.context.settings.options[name] = value), }) - shorthand.absoluteOptions = new Proxy(this.context.settings.absoluteOptions || {}, { + shorthand.absoluteOptions = new Proxy(this.context.settings.absoluteOptions, { get: function (absoluteOptions, name) { if (typeof absoluteOptions[name] === 'undefined') self.context.store.log.warning( @@ -239,9 +238,6 @@ Part.prototype.__boundary = function () { } } catch (err) { this.context.store.log.error(`Could not calculate boundary of \`paths.${key}\``) - this.context.store.log.debug( - `Since \`paths.${key}\` has no boundary, neither does \`parts.${this.name}\`. Ejecting part` - ) return false } } diff --git a/packages/core/src/path.mjs b/packages/core/src/path.mjs index 3689e6ae188..8e6f23954be 100644 --- a/packages/core/src/path.mjs +++ b/packages/core/src/path.mjs @@ -642,7 +642,7 @@ Path.prototype.shiftAlong = function (distance, stepsPerMm = 10) { let thisLen = bezier.length() if (Math.abs(len + thisLen - distance) < 0.1) return op.to if (len + thisLen > distance) - return shiftAlongBezier(distance - len, bezier, thisLen * stepsPerMm) + return __shiftAlongBezier(distance - len, bezier, thisLen * stepsPerMm) len += thisLen } current = op.to @@ -1266,25 +1266,10 @@ function __pathOffset(path, distance, log) { * @private * @param {float} distance - The distance to shift along the cubic Bezier curve * @param {Bezier} bezier - The BezierJs instance - * @param {int} steps - The numer of steps to walk the Bezier with + * @param {int} steps - The numer of steps per mm to walk the Bezier with * @return {Point} point - The point at distance along the cubic Bezier curve */ -function shiftAlongBezier(distance, bezier, steps = false) { - let rlen - if (!steps) { - rlen = new Path() - .move(new Point(...Object.values(bezier.points[0]))) - .curve( - new Point(...Object.values(bezier.points[1])), - new Point(...Object.values(bezier.points[2])), - new Point(...Object.values(bezier.points[3])) - ) - .roughLength() - if (rlen < 2) steps = 20 - else if (rlen < 10) steps = 40 - else if (rlen < 100) steps = 100 - else steps = 200 - } +function __shiftAlongBezier(distance, bezier, steps) { let previous, next, t, thisLen let len = 0 for (let i = 0; i <= steps; i++) { diff --git a/packages/core/src/pattern.mjs b/packages/core/src/pattern.mjs index f5d9e81a753..4d0b30cf606 100644 --- a/packages/core/src/pattern.mjs +++ b/packages/core/src/pattern.mjs @@ -334,7 +334,7 @@ Pattern.prototype.render = function () { this.svg = new Svg(this) this.svg.hooks = this.hooks - return this.__pack().svg.render(this) + return this.__pack().svg.render() } /** diff --git a/packages/core/src/svg.mjs b/packages/core/src/svg.mjs index 4bd8b395984..7e25df2bb80 100644 --- a/packages/core/src/svg.mjs +++ b/packages/core/src/svg.mjs @@ -29,7 +29,7 @@ export function Svg(pattern) { this.attributes.add('xmlns', 'http://www.w3.org/2000/svg') this.attributes.add('xmlns:svg', 'http://www.w3.org/2000/svg') this.attributes.add('xmlns:xlink', 'http://www.w3.org/1999/xlink') - this.attributes.add('xml:lang', pattern.settings.locale) + this.attributes.add('xml:lang', pattern?.settings?.[0]?.locale || 'en') this.attributes.add('xmlns:freesewing', 'http://freesewing.org/namespaces/freesewing') this.attributes.add('freesewing', version) } @@ -44,21 +44,24 @@ export function Svg(pattern) { * @param {Pattern} pattern - The pattern to render * @return {string} svg - The rendered SVG output */ -Svg.prototype.render = function (pattern) { - this.idPrefix = pattern.settings.idPrefix +Svg.prototype.render = function () { + this.idPrefix = this.pattern?.settings?.[0]?.idPrefix || 'fs-' this.__runHooks('preRender') - pattern.__runHooks('postLayout') - if (!pattern.settings.embed) { - this.attributes.add('width', round(pattern.width) + 'mm') - this.attributes.add('height', round(pattern.height) + 'mm') + this.pattern.__runHooks('postLayout') + if (!this.pattern.settings[0].embed) { + this.attributes.add('width', round(this.pattern.width) + 'mm') + this.attributes.add('height', round(this.pattern.height) + 'mm') } - this.attributes.add('viewBox', `0 0 ${pattern.width} ${pattern.height}`) + this.attributes.add('viewBox', `0 0 ${round(this.pattern.width)} ${round(this.pattern.height)}`) this.head = this.__renderHead() this.tail = this.__renderTail() this.svg = '' this.layout = {} // Reset layout - for (let stackId in pattern.stacks) { - const stack = pattern.stacks[stackId] + this.activeStackIndex = 0 + for (let stackId in this.pattern.stacks) { + this.activeStack = stackId + this.idPrefix = this.pattern.settings[this.activeStackIndex].idPrefix + const stack = this.pattern.stacks[stackId] if (!stack.hidden) { const stackSvg = this.__renderStack(stack) this.layout[stackId] = { @@ -69,6 +72,7 @@ Svg.prototype.render = function (pattern) { this.svg += stackSvg this.svg += this.__closeGroup() } + this.activeStackIndex++ } this.svg = this.prefix + this.__renderSvgTag() + this.head + this.svg + this.tail this.__runHooks('postRender') @@ -137,7 +141,7 @@ Svg.prototype.__indent = function () { Svg.prototype.__insertText = function (text) { if (this.hooks.insertText.length > 0) { for (let hook of this.hooks.insertText) - text = hook.method(this.pattern.settings.locale, text, hook.data) + text = hook.method(this.pattern.settings[this.activeStackIndex].locale || 'en', text, hook.data) } return text @@ -223,7 +227,6 @@ Svg.prototype.__renderDefs = function () { */ Svg.prototype.__renderHead = function () { let svg = this.__renderStyle() - svg += this.__renderScript() svg += this.__renderDefs() svg += this.__openGroup(this.idPrefix + 'container') @@ -279,8 +282,8 @@ Svg.prototype.__renderPathText = function (path) { * @param {Part} part - The Part instance to render * @return {string} svg - The SVG markup for the Part object */ -Svg.prototype.__renderPart = function (part, partId) { - let svg = this.__openGroup(`${this.idPrefix}part-${partId}`, part.attributes) +Svg.prototype.__renderPart = function (part) { + let svg = this.__openGroup(`${this.idPrefix}stack-${this.activeStack}-part-${part.name}`, part.attributes) for (let key in part.paths) { let path = part.paths[key] if (!path.hidden) svg += this.__renderPath(path) @@ -302,22 +305,6 @@ Svg.prototype.__renderPart = function (part, partId) { return svg } -/** - * Returns SVG markup for the script block - * - * @private - * @return {string} svg - The SVG markup for the script block - */ -Svg.prototype.__renderScript = function () { - let svg = '' + this.__nl() - - return svg -} - /** * Returns SVG markup for a snippet * diff --git a/packages/core/src/utils.mjs b/packages/core/src/utils.mjs index 75fbba71db5..2d3535da39a 100644 --- a/packages/core/src/utils.mjs +++ b/packages/core/src/utils.mjs @@ -725,15 +725,10 @@ export const __addPartPlugins = (part, config, store) => { store.log.debug(`🔌 __${plugin.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]?.condition) { - store.log.info( - `Plugin \`${plugin.plugin.name}\` was re-requested conditionally. Overwriting earlier condition.` - ) - plugins[plugin.plugin.name] = plugin - } else - store.log.info( - `Plugin \`${plugin.plugin.name}\` was requested conditionally, but is already loaded explicitly. Not loading.` - ) + if (plugins[plugin.plugin.name]?.condition) plugins[plugin.plugin.name] = plugin + else store.log.info( + `Plugin \`${plugin.plugin.name}\` was requested conditionally, but is already loaded explicitly. Not loading.` + ) } else { plugins[plugin.name] = plugin } diff --git a/packages/core/tests/fixtures/render.mjs b/packages/core/tests/fixtures/render.mjs index 776a20f6a7b..af1d7a9181c 100644 --- a/packages/core/tests/fixtures/render.mjs +++ b/packages/core/tests/fixtures/render.mjs @@ -3,366 +3,22 @@ import pkg from '../../package.json' assert { type: 'json' } const { version } = pkg var render = { - boilerplate: ` - - - - - - - - - - - -`, - boilerplateNl: ` - - - - - - - - - - - -`, - embed: ` - - - - - - - - - - - - `, - part: ` - - - - - - - - - - - - - - - - -`, - path: ` - - - - - - - - - - - - - - - - - -`, - text: ` - - - - - - - - - - - - -This is a test - - - - - -`, - circle: ` - - - - - - - - - - - - - - - - -`, - multiText: ` - - - - - - - - - - - - - This is a testwith text onmultiple lines - - - - - - `, - multiTextDflt: ` - - - - - - - - - - - - - This is a testwith text onmultiple lines - - - - - - `, - textOnPath: ` - - - - - - - - - - - - - -This is another test - - - - - -`, - textOnPathCenter: ` - - - - - - - - - - - - - -This is another test - - - - - -`, - textOnPathRight: ` - - - - - - - - - - - - - -This is another test - - - - - -`, - snippet: ` - - - - - - - - - - - - - - - - - -`, - rotatedSnippet: ` - - - - - - - - - - - - - - - - - -` + boilerplate: ``, + boilerplateNl: ``, + embed: ``, + stack: ``, + part: ``, + path: ``, + text: `This is a test `, + multiText: `This is a testwith text onmultiple lines `, + multiTextDflt: `This is a testwith text onmultiple lines `, + textOnPath: `This is another test`, + textOnPathCenter: `This is another test`, + textOnPathRight: `This is another test`, + circle: ``, + snippet: ``, + rotatedSnippet: ``, + scaledSnippet: ``, } export default render diff --git a/packages/core/tests/part.test.mjs b/packages/core/tests/part.test.mjs index 5d3a05af7cf..72040cb1d31 100644 --- a/packages/core/tests/part.test.mjs +++ b/packages/core/tests/part.test.mjs @@ -1,5 +1,5 @@ import chai from 'chai' -import { Design, Part } from '../src/index.mjs' +import { Design, Part, pctBasedOn } from '../src/index.mjs' const expect = chai.expect @@ -75,6 +75,13 @@ describe('Part', () => { expect(units(123.456)).to.equal('12.35cm') }) + it('Should convert units directly', () => { + const part = new Part() + part.context = { settings: { units: 'metric' } } + expect(part.units(123.456)).to.equal('12.35cm') + expect(part.units(123.456)).to.equal('12.35cm') + }) + it('Should set part attributes', () => { const part = new Part() part.attr('foo', 'bar') @@ -136,74 +143,27 @@ describe('Part', () => { name: 'test', draft: ({ points, Point, paths, Path, part }) => { points.from = new Point(123, 456) - points.to = new Point(19, 76) + points.to = new Point(19, 76).attr('data-circle', 12) + paths.test = new Path().move(points.from).line(points.to) return part }, } const design = new Design({ parts: [part] }) const pattern = new design() - pattern.draft() + pattern.draft().render() const boundary = pattern.parts[0].test.__boundary() const { topLeft, bottomRight, width, height } = boundary - expect(topLeft.x).to.equal(19) - expect(topLeft.y).to.equal(76) + expect(topLeft.x).to.equal(7) + expect(topLeft.y).to.equal(64) + // Cover the cached branch + pattern.parts[0].test.__boundary() expect(bottomRight.x).to.equal(123) expect(bottomRight.y).to.equal(456) - expect(width).to.equal(104) - expect(height).to.equal(380) + expect(width).to.equal(116) + expect(height).to.equal(392) }) - /* - it('Should stack a part', () => { - const part = { - name: 'test', - draft: (part) => { - const { points, Point, paths, Path } = part.shorthand() - points.from = new Point(123, 456) - points.to = new Point(19, 76) - paths.test = new Path().move(points.from).line(points.to) - return aprt - } - } - const design = new Design({ parts: [ part ]}) - const pattern = new design({ paperless: true }) - pattern.draft() - pattern.parts.test.home() - console.log(pattern.parts.test.attributes) - expect(part.attributes.get('transform')).to.equal('translate(-17, -74)') - }) - - it('Should only stack a part if needed', () => { - let pattern = new Pattern() - pattern.settings.mode = 'draft' - let part = new pattern.Part() - let short = part.shorthand() - part.points.from = new short.Point(2, 2) - part.points.to = new short.Point(19, 76) - part.paths.test = new short.Path().move(part.points.from).line(part.points.to) - part.home() - expect(part.attributes.get('transform')).to.equal(false) - part.home() - expect(part.attributes.get('transform')).to.equal(false) - }) - it('Should run hooks', () => { - let count = 0 - const design = new Design() - const pattern = new design({ paperless: true }) - const part = pattern.__createPartWithContext() - part.hooks.preDraft = [ - { - method: function () { - count++ - }, - }, - ] - part.runHooks('preDraft') - expect(count).to.equal(1) - }) -*/ - it('Units closure should log a warning when passing a non-number', () => { const part = { name: 'test', @@ -213,79 +173,169 @@ describe('Part', () => { }, } const design = new Design({ parts: [part] }) - const pattern = new design() + // 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( 'Calling `units(value)` but `value` is not a number (`string`)' ) }) - /* - describe('isEmpty', () => { - it('Should return true if the part has no paths or snippets', () => { - const design = new Design() - const pattern = new design() - const part = pattern.__createPartWithContext() - expect(part.isEmpty()).to.be.true - }) - it('Should return true if the part has paths but they have no length', () => { - const design = new Design() - const pattern = new design() - const part = pattern.__createPartWithContext() - const { Path, paths } = part.shorthand() - paths.seam = new Path() - expect(part.isEmpty()).to.be.true - }) - - it("Should return true if the part has paths but they don't render", () => { - const design = new Design() - const pattern = new design() - const part = pattern.__createPartWithContext() - const { Path, paths, Point } = part.shorthand() - paths.seam = new Path().move(new Point(0, 0)).line(new Point(2, 3)).setRender(false) - expect(part.isEmpty()).to.be.true - }) - - it('Should return false if the part has a path with length', () => { - const design = new Design() - const pattern = new design() - const part = pattern.__createPartWithContext() - const { Path, paths, Point } = part.shorthand() - paths.seam = new Path().move(new Point(0, 0)).line(new Point(2, 3)) - - expect(part.isEmpty()).to.be.false - }) - - it('Should return false if the part has a snippet', () => { - const design = new Design() - const pattern = new design() - const part = pattern.__createPartWithContext() - const { Point, snippets, Snippet } = part.shorthand() - snippets.test = new Snippet('test', new Point(0, 0)) - - expect(part.isEmpty()).to.be.false - }) - - it('Should return false if the part has a point that has text', () => { - const design = new Design() - const pattern = new design() - const part = pattern.__createPartWithContext() - const { Point, points } = part.shorthand() - points.test = new Point(0, 0) - points.test.attributes.set('data-text', 'text') - expect(part.isEmpty()).to.be.false - }) - - it('Should return false if the part has a point that has a circle', () => { - const design = new Design() - const pattern = new design() - const part = pattern.__createPartWithContext() - const { Point, points } = part.shorthand() - points.test = new Point(0, 0) - points.test.attributes.set('data-circle', 10) - expect(part.isEmpty()).to.be.false - }) + it('Should (un)hide a part with hide()/unhide()', () => { + const part = new Part() + expect(part.hidden).to.equal(false) + part.hide() + expect(part.hidden).to.equal(true) + part.unhide() + expect(part.hidden).to.equal(false) + }) + + it('Should (un)hide a part with setHidden()', () => { + const part = new Part() + expect(part.hidden).to.equal(false) + part.setHidden(true) + expect(part.hidden).to.equal(true) + part.setHidden(false) + expect(part.hidden).to.equal(false) + }) + + it('Draft method should receive the Snippet constructor', () => { + let method + const part = { + name: 'test', + draft: ({ Point, snippets, Snippet, part }) => { + method = Snippet + snippets.test = new Snippet('notch', new Point(19,80)) + return part + }, + } + const design = new Design({ parts: [part] }) + const pattern = new design() + pattern.draft() + expect(typeof method).to.equal('function') + expect(pattern.parts[0].test.snippets.test.def).to.equal('notch') + expect(pattern.parts[0].test.snippets.test.name).to.equal('test') + expect(pattern.parts[0].test.snippets.test.anchor.x).to.equal(19) + expect(pattern.parts[0].test.snippets.test.anchor.y).to.equal(80) + }) + + it('Measurments proxy should allow setting a measurement', () => { + const part = { + name: 'test', + draft: ({ measurements, part }) => { + measurements.head = 120 + return part + }, + } + const design = new Design({ parts: [part] }) + const pattern = new design() + pattern.draft() + expect(pattern.settings[0].measurements.head).to.equal(120) + }) + + it('Options proxy should allow setting an option', () => { + const part = { + name: 'test', + draft: ({ options, part }) => { + options.test = 120 + return part + }, + } + const design = new Design({ parts: [part] }) + const pattern = new design() + pattern.draft() + expect(pattern.settings[0].options.test).to.equal(120) + }) + + it('AbsoluteOptions proxy should allow setting an absoluteOption', () => { + const part = { + name: 'test', + draft: ({ absoluteOptions, part }) => { + absoluteOptions.test = 120 + return part + }, + } + const design = new Design({ parts: [part] }) + const pattern = new design() + pattern.draft() + expect(pattern.settings[0].absoluteOptions.test).to.equal(120) + }) + + it('Snapped percentage options should be available to the draft method', () => { + const part = { + name: 'test', + options: { + test: { pct: 10, min: 5, max: 25, snap: 5, ...pctBasedOn('head') } + }, + draft: ({ paths, Path, Point, absoluteOptions, part }) => { + paths.test = new Path() + .move(new Point(0,0)) + .line(new Point(absoluteOptions.test, 0)) + return part + }, + } + const design = new Design({ parts: [part] }) + const pattern = new design({ measurements: { head: 200 } }) + pattern.draft() + expect(pattern.parts[0].test.paths.test.ops[1].to.x).to.equal(20) + }) + + it('Accessing unknown option should log a warning', () => { + const part = { + name: 'test', + draft: ({ options, part }) => { + if (options.test || true) return 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`') + }) + + it('Accessing unknown absoluteOption should log a warning', () => { + const part = { + name: 'test', + draft: ({ absoluteOptions, part }) => { + if (absoluteOptions.test || true) return 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`') + }) + + it('Injecting a part should contain all data', () => { + const from = { + name: 'from', + draft: ({ points, Point, paths, Path, snippets, Snippet, part }) => { + points.from = new Point(0,0) + points.to = new Point(19,80) + points.start = new Point(100,100) + points.cp1 = new Point(100,200) + points.cp2 = new Point(200,100) + points.end = new Point(200,200) + paths.line = new Path().move(points.from).line(points.to) + paths.curve = new Path().move(points.start).curve(points.cp1, points.cp2, points.end) + snippets.test = new Snippet('notch', points.end) + return part + }, + } + const to = { + from, + name: 'to', + draft: ({ part }) => part + } + const design = new Design({ parts: [from, to] }) + const pattern = new design() + pattern.draft() + expect(pattern.parts[0].to.points.to.x).to.equal(19) + expect(pattern.parts[0].to.points.to.y).to.equal(80) + expect(typeof pattern.parts[0].to.paths.line).to.equal('object') + expect(pattern.parts[0].to.paths.curve.ops[1].cp2.x).to.equal(200) }) - */ }) diff --git a/packages/core/tests/path.test.mjs b/packages/core/tests/path.test.mjs index 64d6f967bac..a5ad05bf49d 100644 --- a/packages/core/tests/path.test.mjs +++ b/packages/core/tests/path.test.mjs @@ -57,6 +57,63 @@ describe('Path', () => { expect(round(pattern.parts[0].test.paths.test.ops[2].cp1.y)).to.equal(10) }) + it('Should log a warning when passing a non-Point to smurve()', () => { + const part = { + name: 'test', + draft: ({ Point, points, Path, paths, part }) => { + points.from = new Point(10, 20) + points.cp1 = new Point(40, 10) + points.cp2 = new Point(60, 30) + points.to = new Point(90, 20) + + paths.test = new Path() + .move(points.from) + .curve(points.cp1, points.cp2, points.to) + .smurve('hi', 'there') + + return part + }, + } + 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') + }) + + it('Should log a warning when passing a non-Point to smurve_()', () => { + const part = { + name: 'test', + draft: ({ Point, Path, paths, part }) => { + paths.test = new Path().smurve_('hi') + + return 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('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', () => { + const part = { + name: 'test', + draft: ({ paths, part }) => { + paths.test = 'Wriing code can get very lonely sometimes' + + return part + }, + } + 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`') + }) + it('Should offset a line', () => { const part = { name: 'test', @@ -161,13 +218,30 @@ describe('Path', () => { paths.curve = new Path() .move(new Point(0, 0)) .curve(new Point(0, 50), new Point(100, 50), new Point(100, 0)) + .close() return part }, } const design = new Design({ parts: [part] }) const pattern = new design() pattern.draft().render() - expect(round(pattern.parts[0].test.paths.curve.roughLength())).to.equal(200) + expect(round(pattern.parts[0].test.paths.curve.roughLength())).to.equal(300) + }) + + it('Should return the rough length of a line', () => { + const part = { + name: 'test', + draft: ({ paths, Path, Point, part }) => { + paths.line = new Path() + .move(new Point(0, 0)) + .line(new Point(0, 50)) + return part + }, + } + const design = new Design({ parts: [part] }) + const pattern = new design() + pattern.draft().render() + expect(round(pattern.parts[0].test.paths.line.roughLength())).to.equal(50) }) it('Should return the path start point', () => { @@ -615,6 +689,32 @@ describe('Path', () => { expect(round(line.to.y)).to.equal(46.98) }) + it('Should split a path on a line joint', () => { + const a = new Point(45, 60) + const b = new Point(10, 30) + const c = new Point(90, 30) + const test = new Path().move(a).line(b).line(c) + + let halves = test.split(b) + expect(halves[0].ops[1].to.x).to.equal(10) + expect(halves[0].ops[1].to.y).to.equal(30) + expect(halves[1].ops[0].to.x).to.equal(10) + expect(halves[1].ops[0].to.y).to.equal(30) + }) + + it('Should split a path on a curve joint', () => { + const a = new Point(45, 60) + const b = new Point(10, 30) + const c = new Point(90, 30) + const test = new Path().move(a)._curve(b,b)._curve(c,c) + + let halves = test.split(b) + expect(halves[0].ops[1].to.x).to.equal(10) + expect(halves[0].ops[1].to.y).to.equal(30) + expect(halves[1].ops[0].to.x).to.equal(10) + expect(halves[1].ops[0].to.y).to.equal(30) + }) + it('Should trim a path when lines overlap', () => { const A = new Point(0, 0) const B = new Point(100, 100) @@ -684,6 +784,16 @@ describe('Path', () => { expect(test.ops[1].to.y).to.equal(20) }) + it('Calling translate with non-numbers should generate a warning', () => { + const log = [] + const p = new Path() + p.log = { warning: msg => log.push(msg) } + p.translate('a', 'b') + expect(log.length).to.equal(2) + expect(log[0]).to.equal('Called `Path.translate(x, y)` but `x` is not a number') + expect(log[1]).to.equal('Called `Path.translate(x, y)` but `y` is not a number') + }) + it('Should add a path attribute', () => { const line = new Path() .move(new Point(0, 0)) @@ -1039,7 +1149,7 @@ describe('Path', () => { it('Should log a warning when splitting a path on a non-point', () => { const part = { name: 'test', - draft: ({ Path, Point, points }) => { + draft: ({ Path, Point, points, part}) => { points.a = new Path().move(new Point(0, 0)).line(new Point(0, 40)).split() return part }, @@ -1051,4 +1161,31 @@ describe('Path', () => { 'Called `Path.split(point)` but `point` is not a `Point` object' ) }) + + it('Should add a class', () => { + const part = { + name: 'test', + draft: ({ Path, paths, Point, points, part }) => { + paths.line = new Path() + .move(new Point(0,0)) + .line(new Point(10,10)) + .addClass('fabric banana') + return part + }, + } + const design = new Design({ parts: [part] }) + const pattern = new design() + pattern.draft() + expect(pattern.parts[0].test.paths.line.attributes.get('class')).to.equal('fabric banana') + }) + + it('Should (un)hide a path with hide()/unhide()', () => { + const path = new Path() + expect(path.hidden).to.equal(false) + path.hide() + expect(path.hidden).to.equal(true) + path.unhide() + expect(path.hidden).to.equal(false) + }) + }) diff --git a/packages/core/tests/pattern-draft.test.mjs b/packages/core/tests/pattern-draft.test.mjs index 183c4806067..d7257a96acf 100644 --- a/packages/core/tests/pattern-draft.test.mjs +++ b/packages/core/tests/pattern-draft.test.mjs @@ -48,7 +48,7 @@ describe('Pattern', () => { name: 'test.partB', measurements: ['head', 'knee'], optionalMeasurements: ['knee'], - after: partA, + after: [ partA ], plugins: [ { name: 'testPlugin', diff --git a/packages/core/tests/pattern-init.test.mjs b/packages/core/tests/pattern-init.test.mjs index f64c8d7eae7..4da857b2497 100644 --- a/packages/core/tests/pattern-init.test.mjs +++ b/packages/core/tests/pattern-init.test.mjs @@ -580,6 +580,51 @@ describe('Pattern', () => { expect(pattern.hooks.preRender.length).to.equal(1) }) + it('Load conditional plugins that are also passing data', () => { + const plugin1 = { + name: 'example1', + version: 1, + hooks: { + preRender: function (svg) { + svg.attributes.add('freesewing:plugin-example1', 1) + }, + }, + } + const plugin2 = { + name: 'example2', + version: 2, + hooks: { + preRender: function (svg) { + svg.attributes.add('freesewing:plugin-example2', 1) + }, + }, + } + const condition1 = () => true + const condition2 = () => false + const part1 = { + name: 'part1', + plugins: [ + [plugin1, {} ], + { plugin: plugin2, condition: condition1 } + ], + draft: ({ part }) => part + } + const part2 = { + name: 'part2', + plugins: [ + plugin2, + { plugin: plugin2, condition: condition2 }, + ], + draft: ({ part }) => part + } + const design = new Design({ + parts: [ part1, part2 ] + }) + const pattern = new design() + pattern.init() + 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() diff --git a/packages/core/tests/svg.test.mjs b/packages/core/tests/svg.test.mjs index 1423f2335c1..d44992df897 100644 --- a/packages/core/tests/svg.test.mjs +++ b/packages/core/tests/svg.test.mjs @@ -1,43 +1,45 @@ import chai from 'chai' import chaiString from 'chai-string' -//import { Design, Pattern } from '../src/index.mjs' -//import pkg from '../package.json' assert { type: 'json' } -//import render from './fixtures/render.mjs' +import { Svg } from '../src/svg.mjs' +import { Design, Pattern, Attributes } from '../src/index.mjs' +import { version } from '../data.mjs' +import render from './fixtures/render.mjs' chai.use(chaiString) const expect = chai.expect -//const { version } = pkg -it('FIXME: Write some tests here', () => { - expect(true).to.equal(true) -}) - -/* -describe('Svg', () => { +const getPattern = (settings={}, draft=false) => { const part = { name: 'test', - draft: part => { - const { paths, Path, Point, points } = part.shorthand() - points.a = new Path() - .move(new Point(0, 0)) - .line(new Point(0, 40)) - .shiftFractionAlong() - return part - } + draft: draft + ? draft + : ({ paths, Path, Point, part }) => { + paths.test = new Path() + .move(new Point(0, 0)) + .line(new Point(40, 20)) + .curve(new Point(12, 34), new Point(56, 78), new Point(21, 32)) + .close() + .attr('id', 'something') + .attr('class', 'freesewing') + return part + } } - const design = new Design({ parts: [ part ] }) - const pattern = new design() + const Pattern = new Design({ parts: [ part ] }) + + return new Pattern(settings) +} + +const trim = svg => svg.split("\n").map(line => line.trim()).join('') + +describe('Svg', () => { it('Svg constructor should initialize object', () => { - pattern.render() - let svg = pattern.svg - expect(svg.openGroups).to.eql([]) + const svg = new Svg() + expect(svg.attributes instanceof Attributes).to.equal(true) expect(svg.freeId).to.equal(0) expect(svg.body).to.equal('') expect(svg.style).to.equal('') - expect(svg.script).to.equal('') expect(svg.defs).to.equal('') - expect(svg.pattern).to.eql(pattern) expect(svg.prefix).to.equal('') expect(svg.attributes.get('xmlns')).to.equal('http://www.w3.org/2000/svg') expect(svg.attributes.get('xmlns:svg')).to.equal('http://www.w3.org/2000/svg') @@ -48,47 +50,233 @@ describe('Svg', () => { expect(svg.attributes.get('freesewing')).to.equal(version) }) - it('Should render Svg boilerplate', () => { - let pattern = new Pattern() - expect(pattern.render()).to.equalIgnoreSpaces(render.boilerplate) + it('Svg constructor should use the object we pass it as pattern', () => { + const obj = {} + const svg = new Svg(obj) + expect(svg.pattern).to.eql(obj) }) - it('Should render language attribute', () => { - let pattern = new Pattern() - pattern.settings.locale = 'nl' - expect(pattern.render()).to.equalIgnoreSpaces(render.boilerplateNl) + + it('Should render a pattern as SVG', () => { + const pattern = getPattern() + const svg = pattern.draft().render() + expect(trim(svg)).to.equalIgnoreSpaces(render.boilerplate) }) - it('Should render Svg boilerplate for embedding', () => { - let pattern = new Pattern() - pattern.settings.embed = true - expect(pattern.render()).to.equalIgnoreSpaces(render.embed) + it('Should render the SVG language attribute', () => { + const pattern = getPattern({ locale: 'nl' }) + const svg = pattern.draft().render() + expect(svg).to.equalIgnoreSpaces(render.boilerplateNl) }) - it('Should render Svg part boilerplate', () => { - let pattern = new Pattern() - pattern.render() - pattern.parts.test = new pattern.Part() - expect(pattern.render()).to.equalIgnoreSpaces(render.part) - pattern.parts.test.render = false - expect(pattern.render()).to.equalIgnoreSpaces(render.boilerplate) + it('Should render the SVG viewBox attribute for embedding', () => { + const pattern = getPattern({ embed: true }) + const svg = pattern.draft().render() + expect(trim(svg)).to.equalIgnoreSpaces(render.embed) }) - it('Should render Svg path', () => { - let pattern = new Pattern() - pattern.render() - pattern.parts.test = new pattern.Part() - let p = pattern.parts.test - p.paths.test = new p.Path() - .move(new p.Point(0, 0)) - .line(new p.Point(40, 20)) - .curve(new p.Point(12, 34), new p.Point(56, 78), new p.Point(21, 32)) - .close() - .attr('id', 'something') - .attr('class', 'freesewing') - expect(pattern.render()).to.equalIgnoreSpaces(render.path) + it('Should render a stack as SVG', () => { + const pattern = getPattern() + pattern.draft().render() + const svg = pattern.svg.__renderStack(pattern.stacks.test) + expect(trim(svg)).to.equalIgnoreSpaces(render.part) }) - it('Should not render Svg path when render property is false', () => { + + it('Should render a part as SVG', () => { + const pattern = getPattern() + pattern.draft().render() + const svg = pattern.svg.__renderPart(pattern.parts[0].test) + expect(trim(svg)).to.equalIgnoreSpaces(render.part) + }) + + it('Should render a path as SVG', () => { + const pattern = getPattern() + pattern.draft().render() + const svg = pattern.svg.__renderPath(pattern.parts[0].test.paths.test) + expect(trim(svg)).to.equalIgnoreSpaces(render.path) + }) + + it('Should render Svg text', () => { + const pattern = getPattern({}, ({ points, Point, part }) => { + points.test = new Point(20, 20) + .attr('data-text', 'This is a test') + .attr('data-text-class', 'text-lg') + points.other = new Point(10, 10).attr('data-text', '') + + return part + }) + pattern.draft().render() + const svg = pattern.svg.__renderPart(pattern.parts[0].test) + expect(trim(svg)).to.equalIgnoreSpaces(render.text) + }) + + it('Should render Svg multi-line text', () => { + const pattern = getPattern({}, ({ points, Point, part }) => { + points.test = new Point(20, 20) + .attr('data-text', 'This is a test\nwith text on\nmultiple lines') + .attr('data-text-class', 'text-lg') + .attr('data-text-lineheight', 8) + + return part + }) + pattern.draft().render() + const svg = pattern.svg.__renderPart(pattern.parts[0].test) + expect(trim(svg)).to.equalIgnoreSpaces(render.multiText) + }) + + it('Should render Svg multi-line text with default lineheight', () => { + const pattern = getPattern({}, ({ points, Point, part }) => { + points.test = new Point(20, 20) + .attr('data-text', 'This is a test\nwith text on\nmultiple lines') + .attr('data-text-class', 'text-lg') + + return part + }) + pattern.draft().render() + const svg = pattern.svg.__renderPart(pattern.parts[0].test) + expect(trim(svg)).to.equalIgnoreSpaces(render.multiTextDflt) + }) + + + it('Should render Svg text on path', () => { + const pattern = getPattern({}, ({ paths, Path, Point, part }) => { + paths.test = new Path() + .move(new Point(0, 0)) + .line(new Point(40, 20)) + .curve(new Point(12, 34), new Point(56, 78), new Point(21, 32)) + .close() + .attr('data-text', 'This is another test') + .attr('data-text-class', 'text-sm') + .attr('class', 'freesewing') + + return part + }) + pattern.draft().render() + const svg = pattern.svg.__renderPart(pattern.parts[0].test) + expect(trim(svg)).to.equalIgnoreSpaces(render.textOnPath) + }) + + it('Should render Svg text on path, center aligned', () => { + const pattern = getPattern({}, ({ paths, Path, Point, part }) => { + paths.test = new Path() + .attr('data-text', 'This is another test') + .attr('data-text-class', 'center') + .attr('class', 'freesewing') + + return part + }) + pattern.draft().render() + const svg = pattern.svg.__renderPart(pattern.parts[0].test) + expect(trim(svg)).to.equalIgnoreSpaces(render.textOnPathCenter) + }) + + it('Should render Svg text on path, right aligned', () => { + const pattern = getPattern({}, ({ paths, Path, Point, part }) => { + paths.test = new Path() + .attr('data-text', 'This is another test') + .attr('data-text-class', 'right') + .attr('class', 'freesewing') + + return part + }) + pattern.draft().render() + const svg = pattern.svg.__renderPart(pattern.parts[0].test) + expect(trim(svg)).to.equalIgnoreSpaces(render.textOnPathRight) + }) + + it('Should render an Svg circle', () => { + const pattern = getPattern({}, ({ points, Point, part }) => { + points.test = new Point(20, 20).attr('data-circle', '50') + + return part + }) + pattern.draft().render() + const svg = pattern.svg.__renderPart(pattern.parts[0].test) + expect(trim(svg)).to.equalIgnoreSpaces(render.circle) + }) + + it('Should render an Svg snippet', () => { + const pattern = getPattern({}, ({ snippets, Snippet, Point, part }) => { + snippets.test = new Snippet('test', new Point(20, 20), 'This is a snippet') + + return part + }) + pattern.draft().render() + const svg = pattern.svg.__renderPart(pattern.parts[0].test) + expect(trim(svg)).to.equalIgnoreSpaces(render.snippet) + }) + + it('Should render a rotated Svg snippet', () => { + const pattern = getPattern({}, ({ snippets, Snippet, Point, part }) => { + snippets.test = new Snippet('test', new Point(20, 20), 'This is a snippet') + .attr( 'data-rotate', 90) + + return part + }) + pattern.draft().render() + const svg = pattern.svg.__renderPart(pattern.parts[0].test) + expect(trim(svg)).to.equalIgnoreSpaces(render.rotatedSnippet) + }) + + it('Should replaced double quotes in Svg text', () => { + const svg = new Svg() + expect(svg.__escapeText('This is a "test" message')).to.equal( + 'This is a “test“ message' + ) + }) + + it('Should scale an Svg snippet', () => { + const pattern = getPattern({}, ({ snippets, Snippet, Point, part }) => { + snippets.test = new Snippet('test', new Point(20, 20), 'This is a snippet') + .attr( 'data-scale', 2) + + return part + }) + pattern.draft().render() + const svg = pattern.svg.__renderPart(pattern.parts[0].test) + expect(trim(svg)).to.equalIgnoreSpaces(render.scaledSnippet) + }) + + it('Should run preRender hook', () => { + const pattern = getPattern() + pattern.on('preRender', (svg) => { + svg.attributes.set('data-hook', 'preRender') + }) + pattern.draft().render() + expect(pattern.svg.attributes.get('data-hook')).to.equal('preRender') + }) + + it('Should run insertText hook', () => { + const pattern = getPattern({}, ({ points, Point, part }) => { + points.test = new Point(20, 20) + .attr('data-text', 'This is a test') + .attr('data-text-class', 'text-lg') + + return part + }) + pattern.on('insertText', (locale, text) => { + return text.toUpperCase() + }) + pattern.draft() + expect(pattern.render()).to.contain('THIS IS A TEST') + }) + + it('Should run postRender hook', () => { + const pattern = getPattern() + pattern.on('postRender', (svg) => { + svg.svg = 'test' + }) + expect(pattern.render()).to.equal('test') + }) + + it('Should tab in and out', () => { + const svg = new Svg() + svg.tabs = 2 + expect(svg.__tab()).to.equal(' ') + }) + + /* + it('Should not render an Svg path when render property is false', () => { let pattern = new Pattern() pattern.render() pattern.parts.test = new pattern.Part() @@ -103,185 +291,5 @@ describe('Svg', () => { p.paths.test.render = false expect(pattern.render()).to.equalIgnoreSpaces(render.part) }) - - it('Should render Svg text', () => { - let pattern = new Pattern() - pattern.render() - pattern.parts.test = new pattern.Part() - let p = pattern.parts.test - p.points.test = new p.Point(20, 20) - .attr('data-text', 'This is a test') - .attr('data-text-class', 'text-lg') - p.points.other = new p.Point(10, 10).attr('data-text', '') - expect(pattern.render()).to.equalIgnoreSpaces(render.text) - }) - - it('Should render Svg multi-line text', () => { - let pattern = new Pattern() - pattern.render() - pattern.parts.test = new pattern.Part() - let p = pattern.parts.test - p.points.test = new p.Point(20, 20) - .attr('data-text', 'This is a test\nwith text on\nmultiple lines') - .attr('data-text-class', 'text-lg') - .attr('data-text-lineheight', 8) - expect(pattern.render()).to.equalIgnoreSpaces(render.multiText) - }) - - it('Should render Svg multi-line text with default lineheight', () => { - let pattern = new Pattern() - pattern.render() - pattern.parts.test = new pattern.Part() - let p = pattern.parts.test - p.points.test = new p.Point(20, 20) - .attr('data-text', 'This is a test\nwith text on\nmultiple lines') - .attr('data-text-class', 'text-lg') - expect(pattern.render()).to.equalIgnoreSpaces(render.multiTextDflt) - }) - - it('Should not render text when there is none', () => { - let pattern = new Pattern() - pattern.render() - pattern.parts.test = new pattern.Part() - let p = pattern.parts.test - p.points.test = new p.Point(20, 20) - expect(pattern.render()).to.equalIgnoreSpaces(render.part) - }) - - it('Should render Svg text on path', () => { - let pattern = new Pattern() - pattern.render() - pattern.parts.test = new pattern.Part() - let p = pattern.parts.test - p.paths.test = new p.Path() - .move(new p.Point(0, 0)) - .line(new p.Point(40, 20)) - .curve(new p.Point(12, 34), new p.Point(56, 78), new p.Point(21, 32)) - .close() - .attr('data-text', 'This is another test') - .attr('data-text-class', 'text-sm') - .attr('class', 'freesewing') - expect(pattern.render()).to.equalIgnoreSpaces(render.textOnPath) - }) - - it('Should render Svg text on path, center aligned', () => { - let pattern = new Pattern() - pattern.render() - pattern.parts.test = new pattern.Part() - let p = pattern.parts.test - p.paths.test = new p.Path() - .move(new p.Point(0, 0)) - .line(new p.Point(40, 20)) - .curve(new p.Point(12, 34), new p.Point(56, 78), new p.Point(21, 32)) - .close() - .attr('data-text', 'This is another test') - .attr('data-text-class', 'center') - .attr('class', 'freesewing') - expect(pattern.render()).to.equalIgnoreSpaces(render.textOnPathCenter) - }) - - it('Should render Svg text on path, right aligned', () => { - let pattern = new Pattern() - pattern.render() - pattern.parts.test = new pattern.Part() - let p = pattern.parts.test - p.paths.test = new p.Path() - .move(new p.Point(0, 0)) - .line(new p.Point(40, 20)) - .curve(new p.Point(12, 34), new p.Point(56, 78), new p.Point(21, 32)) - .close() - .attr('data-text', 'This is another test') - .attr('data-text-class', 'right') - .attr('class', 'freesewing') - expect(pattern.render()).to.equalIgnoreSpaces(render.textOnPathRight) - }) - - it('Should render an Svg circle', () => { - let pattern = new Pattern() - pattern.render() - pattern.parts.test = new pattern.Part() - let p = pattern.parts.test - p.points.test = new p.Point(20, 20).attr('data-circle', '50') - expect(pattern.render()).to.equalIgnoreSpaces(render.circle) - }) - - it('Should render an Svg snippet', () => { - let pattern = new Pattern() - pattern.render() - pattern.parts.test = new pattern.Part() - let p = pattern.parts.test - p.snippets.test = new p.Snippet('test', new p.Point(20, 20), 'This is a snippet') - expect(pattern.render()).to.equalIgnoreSpaces(render.snippet) - }) - - it('Should render a rotated Svg snippet', () => { - let pattern = new Pattern() - pattern.render() - pattern.parts.test = new pattern.Part() - let p = pattern.parts.test - p.snippets.test = new p.Snippet('test', new p.Point(20, 20), 'This is a snippet').attr( - 'data-rotate', - 90 - ) - expect(pattern.render()).to.equalIgnoreSpaces(render.rotatedSnippet) - }) - - it('Should replaced double quotes in Svg text', () => { - const pattern = new Pattern() - pattern.render() - expect(pattern.svg.escapeText('This is a "test" message')).to.equal( - 'This is a “test“ message' - ) - }) - - it('Should scale an Svg snippet', () => { - let pattern = new Pattern() - pattern.render() - pattern.parts.test = new pattern.Part() - let p = pattern.parts.test - p.snippets.test = new p.Snippet('test', new p.Point(20, 20), 'This is a snippet').attr( - 'data-scale', - 2 - ) - expect(pattern.render()).to.contain('scale(2)') - }) - - it('Should run preRender hook', () => { - let pattern = new Pattern() - pattern.on('preRender', (svg) => { - svg.attributes.set('data-hook', 'preRender') - }) - pattern.render() - expect(pattern.svg.attributes.get('data-hook')).to.equal('preRender') - }) - - it('Should run insertText hook', () => { - let pattern = new Pattern() - pattern.on('insertText', (locale, text) => { - return text.toUpperCase() - }) - pattern.parts.test = new pattern.Part() - let p = pattern.parts.test - p.points.test = new p.Point(20, 20) - .attr('data-text', 'This is a test') - .attr('data-text-class', 'text-lg') - expect(pattern.render()).to.contain('THIS IS A TEST') - }) - - it('Should run postRender hook', () => { - let pattern = new Pattern() - pattern.on('postRender', (svg) => { - svg.svg = 'test' - }) - expect(pattern.render()).to.equal('test') - }) - - it('Should tab in and out', () => { - let pattern = new Pattern() - pattern.render() - const svg = pattern.svg - svg.tabs = 2 - expect(svg.tab()).to.equal(' ') - }) -}) */ +}) diff --git a/packages/core/tests/utils.test.mjs b/packages/core/tests/utils.test.mjs index a80ce914b1a..e1a4a3c4828 100644 --- a/packages/core/tests/utils.test.mjs +++ b/packages/core/tests/utils.test.mjs @@ -1,6 +1,7 @@ import chai from 'chai' import { Point, + Design, capitalize, beamsIntersect, linesIntersect, @@ -23,6 +24,7 @@ import { deg2rad, rad2deg, pctBasedOn, + generateStackTransform, } from '../src/index.mjs' const { expect } = chai @@ -474,29 +476,20 @@ describe('Utils', () => { expect(result.toAbs(0.0123, { measurements })).to.equal(12.3) expect(result.fromAbs(12.3, { measurements })).to.equal(0.0123) }) - /* - it('Should generate a part transform', () => { - const part = { + it('Should generate a stack transform', () => { + const test = { name: 'test', - draft: part => { - const { points, Point, paths, Path } = part.shorthand() + draft: ({ points, Point, paths, Path, part }) => { points.from = new Point(2, 2) points.to = new Point(19, 76) paths.test = new Path().move(points.from).line(points.to) return part } } - const design = new Design({ parts: [ part ]}) + const design = new Design({ parts: [ test ]}) const pattern = new design() - pattern.draft().render() - const transform = generatePartTransform(30, 60, 90, true, true, pattern.__parts.test) - expect(transform.transform).to.equal( - `translate(${30 + part.topLeft.x + part.bottomRight.x} ${ - 60 + part.topLeft.y + part.bottomRight.y - }) scale(-1 -1) rotate(90 ${part.topLeft.x + part.width / 2} ${ - part.topLeft.y + part.height / 2 - })` - ) + const props = pattern.draft().getRenderProps() + const transform = generateStackTransform(30, 60, 90, true, true, props.stacks.test) + expect(transform.transform).to.equal('translate(51 138) scale(-1 -1) rotate(90 10.5 39)') }) - */ })