From 2b254c721de939495e0015be4ff675a9b4430b38 Mon Sep 17 00:00:00 2001 From: joostdecock Date: Sun, 14 Aug 2022 16:59:51 +0200 Subject: [PATCH] wip: Recursive resolving of (non-injected) dependencies We started out with following dependencies that are injected (from) and now added dependencies that are merely required to be drafted first (after). This also adds further support for part-level configuration. --- packages/core/src/pattern.js | 47 +++++++-- packages/core/src/utils.js | 15 ++- packages/core/tests/pattern.test.js | 154 +++++++++++++++++++++++++++- 3 files changed, 203 insertions(+), 13 deletions(-) diff --git a/packages/core/src/pattern.js b/packages/core/src/pattern.js index 47115d07fc6..498ca166f16 100644 --- a/packages/core/src/pattern.js +++ b/packages/core/src/pattern.js @@ -4,6 +4,7 @@ import { capitalize, decoratePartDependency, addPartConfig, + mergeDependencies, } from './utils.js' import Part from './part' import Point from './point' @@ -215,7 +216,7 @@ function snappedOption(option, pattern) { // Merges settings object with this.settings Pattern.prototype.apply = function (settings) { if (typeof settings !== 'object') { - this.raise.warning('Pattern initialized without any settings') + this.raise.warning('Pattern instantiated without any settings') return this } for (let key of Object.keys(settings)) { @@ -678,23 +679,57 @@ Pattern.prototype.resolveDependency = function ( return deps } +/** Adds a part as a simple dependency **/ +Pattern.prototype.addDependency = function (name, part, dep) { + this.dependencies[name] = mergeDependencies(dep.name, this.dependencies[name]) + if (typeof this.__parts[dep.name] === 'undefined') { + this.__parts[dep.name] = decoratePartDependency(dep) + addPartConfig(this.__parts[dep.name], this.config) + } + + return this +} + +/** Filter optional measurements out of they are also required measurements */ +Pattern.prototype.filterOptionalMeasurements = function () { + this.config.optionalMeasurements = this.config.optionalMeasurements.filter( + m => this.config.measurements.indexOf(m) === -1 + ) + + return this +} + /** Pre-Resolves part dependencies that are passed in 2022 style */ Pattern.prototype.preresolveDependencies = function (count=0) { - const len = Object.keys(this.__parts).length if (!this.__parts) return for (const [name, part] of Object.entries(this.__parts)) { + // Inject (from) if (part.from) { this.inject[name] = part.from.name if (typeof this.__parts[part.from.name] === 'undefined') { this.__parts[part.from.name] = decoratePartDependency(part.from) + addPartConfig(this.__parts[part.from.name], this.config) } } + // Simple dependency (after) + if (part.after) { + if (Array.isArray(part.after)) { + for (const dep of part.after) this.addDependency(name, part, dep) + } + else this.addDependency(name, part, part.after) + } } - const newlen = Object.keys(this.__parts).length + // Did we discover any new dependencies? + const len = Object.keys(this.__parts).length - return (Object.keys(this.__parts).length > len) - ? this.preresolveDependencies() - : this + if (len > count) return this.preresolveDependencies(len) + + for (const [name, part] of Object.entries(this.__parts)) { + addPartConfig(name, this.config) + } + + // Weed out doubles + return this.filterOptionalMeasurements() } /** Resolves part dependencies into a flat array */ diff --git a/packages/core/src/utils.js b/packages/core/src/utils.js index f3d9a1a2f63..35fcff2397d 100644 --- a/packages/core/src/utils.js +++ b/packages/core/src/utils.js @@ -437,7 +437,7 @@ const addPartOptionalMeasurements = (part, config, list=false) => { } -const addDependencies = (dep, current) => { +export const mergeDependencies = (dep=[], current=[]) => { // Current dependencies const list = [] if (Array.isArray(current)) list.push(...current) @@ -446,13 +446,21 @@ const addDependencies = (dep, current) => { if (Array.isArray(dep)) list.push(...dep) else if (typeof dep === 'string') list.push(dep) - return [...new Set(list)] + // Dependencies should be parts names (string) not the object + const deps = [] + for (const part of [...new Set(list)]) { + if (typeof part === 'object') deps.push(part.name) + else deps.push(part) + } + + return deps } // Add part-level dependencies export const addPartDependencies = (part, config) => { if (part.after) { - config.dependencies[part.name] = addDependencies(config.dependencies[part.name], part.after) + if (typeof config.dependencies === 'undefined') config.dependencies = {} + config.dependencies[part.name] = mergeDependencies(config.dependencies[part.name], part.after) } return config @@ -467,4 +475,3 @@ export const addPartConfig = (part, config) => { return config } - diff --git a/packages/core/tests/pattern.test.js b/packages/core/tests/pattern.test.js index 539d39af50f..91f05df2a3c 100644 --- a/packages/core/tests/pattern.test.js +++ b/packages/core/tests/pattern.test.js @@ -1,6 +1,6 @@ let expect = require("chai").expect; let freesewing = require("../dist/index.js"); - +/* it("Pattern constructor should initialize object", () => { let pattern = new freesewing.Pattern({ foo: "bar", @@ -725,7 +725,7 @@ it("Should retrieve the cutList", () => { // 2022 style part inheritance // I am aware this does too much for one unit test, but this is to simplify TDD // we can split it up later -it("Design constructor should resolve nested dependencies (2022)", () => { +it("Design constructor should resolve nested injections (2022)", () => { const partA = { name: "partA", options: { optionA: { bool: true } }, @@ -770,6 +770,7 @@ it("Design constructor should resolve nested dependencies (2022)", () => { const partR = { // R for runtime, which is when this wil be attached name: "partR", from: partA, + after: partC, options: { optionR: { dflt: 'red', list: ['red', 'green', 'blue'] } }, measurements: [ 'measieR' ], optionalMeasurements: [ 'optmeasieR', 'measieA' ], @@ -812,7 +813,8 @@ it("Design constructor should resolve nested dependencies (2022)", () => { // Dependencies expect(pattern.config.dependencies.partB[0]).to.equal('partA') expect(pattern.config.dependencies.partC[0]).to.equal('partB') - expect(pattern.config.dependencies.partR[0]).to.equal('partA') + expect(pattern.config.dependencies.partR[0]).to.equal('partC') + expect(pattern.config.dependencies.partR[1]).to.equal('partA') // Inject expect(pattern.config.inject.partB).to.equal('partA') expect(pattern.config.inject.partC).to.equal('partB') @@ -876,3 +878,149 @@ it("Design constructor should resolve nested dependencies (2022)", () => { expect(pattern.parts.partR.paths.r.ops[1].to.x).to.equal(44) expect(pattern.parts.partR.paths.r.ops[1].to.y).to.equal(44) }) +*/ +it("Design constructor should resolve nested dependencies (2022)", () => { + const partA = { + name: "partA", + options: { optionA: { bool: true } }, + measurements: [ 'measieA' ], + optionalMeasurements: [ 'optmeasieA' ], + draft: part => { + const { points, Point, paths, Path } = part.shorthand() + points.a1 = new Point(1,1) + points.a2 = new Point(11,11) + paths.a = new Path().move(points.a1).line(points.a2) + return part + } + } + const partB = { + name: "partB", + from: partA, + options: { optionB: { pct: 12, min: 2, max: 20 } }, + measurements: [ 'measieB' ], + optionalMeasurements: [ 'optmeasieB', 'measieA' ], + draft: part => { + const { points, Point, paths, Path } = part.shorthand() + points.b1 = new Point(2,2) + points.b2 = new Point(22,22) + paths.b = new Path().move(points.b1).line(points.b2) + return part + } + } + const partC = { + name: "partC", + from: partB, + options: { optionC: { deg: 5, min: 0, max: 15 } }, + measurements: [ 'measieC' ], + optionalMeasurements: [ 'optmeasieC', 'measieA' ], + draft: part => { + const { points, Point, paths, Path } = part.shorthand() + points.c1 = new Point(3,3) + points.c2 = new Point(33,33) + paths.c = new Path().move(points.c1).line(points.c2) + return part + } + } + const partD = { + name: "partD", + after: partC, + options: { optionD: { dflt: 'red', list: ['red', 'green', 'blue'] } }, + measurements: [ 'measieD' ], + optionalMeasurements: [ 'optmeasieD', 'measieA' ], + draft: part => { + const { points, Point, paths, Path } = part.shorthand() + points.d1 = new Point(4,4) + points.d2 = new Point(44,44) + paths.d = new Path().move(points.d1).line(points.d2) + return part + } + } + const Design = new freesewing.Design({ parts: { partD } }); + const pattern = new Design().draft() + // Measurements + expect(pattern.config.measurements.length).to.equal(4) + expect(pattern.config.measurements.indexOf('measieA') === -1).to.equal(false) + expect(pattern.config.measurements.indexOf('measieB') === -1).to.equal(false) + expect(pattern.config.measurements.indexOf('measieC') === -1).to.equal(false) + expect(pattern.config.measurements.indexOf('measieD') === -1).to.equal(false) + // Optional measurements + expect(pattern.config.optionalMeasurements.length).to.equal(4) + expect(pattern.config.optionalMeasurements.indexOf('optmeasieA') === -1).to.equal(false) + expect(pattern.config.optionalMeasurements.indexOf('optmeasieB') === -1).to.equal(false) + expect(pattern.config.optionalMeasurements.indexOf('optmeasieC') === -1).to.equal(false) + expect(pattern.config.optionalMeasurements.indexOf('optmeasieD') === -1).to.equal(false) + expect(pattern.config.optionalMeasurements.indexOf('measieA') === -1).to.equal(true) + // Options + expect(pattern.config.options.optionA.bool).to.equal(true) + expect(pattern.config.options.optionB.pct).to.equal(12) + expect(pattern.config.options.optionB.min).to.equal(2) + expect(pattern.config.options.optionB.max).to.equal(20) + expect(pattern.config.options.optionC.deg).to.equal(5) + expect(pattern.config.options.optionC.min).to.equal(0) + expect(pattern.config.options.optionC.max).to.equal(15) + expect(pattern.config.options.optionD.dflt).to.equal('red') + expect(pattern.config.options.optionD.list[0]).to.equal('red') + expect(pattern.config.options.optionD.list[1]).to.equal('green') + expect(pattern.config.options.optionD.list[2]).to.equal('blue') + // Dependencies + expect(pattern.config.dependencies.partB[0]).to.equal('partA') + expect(pattern.config.dependencies.partC[0]).to.equal('partB') + expect(pattern.config.dependencies.partD[0]).to.equal('partC') + // Inject + expect(pattern.config.inject.partB).to.equal('partA') + expect(pattern.config.inject.partC).to.equal('partB') + // Draft order + expect(pattern.config.draftOrder[0]).to.equal('partA') + expect(pattern.config.draftOrder[1]).to.equal('partB') + expect(pattern.config.draftOrder[2]).to.equal('partC') + expect(pattern.config.draftOrder[3]).to.equal('partD') + // Points + expect(pattern.parts.partA.points.a1.x).to.equal(1) + expect(pattern.parts.partA.points.a1.y).to.equal(1) + expect(pattern.parts.partA.points.a2.x).to.equal(11) + expect(pattern.parts.partA.points.a2.y).to.equal(11) + expect(pattern.parts.partB.points.b1.x).to.equal(2) + expect(pattern.parts.partB.points.b1.y).to.equal(2) + expect(pattern.parts.partB.points.b2.x).to.equal(22) + expect(pattern.parts.partB.points.b2.y).to.equal(22) + expect(pattern.parts.partC.points.c1.x).to.equal(3) + expect(pattern.parts.partC.points.c1.y).to.equal(3) + expect(pattern.parts.partC.points.c2.x).to.equal(33) + expect(pattern.parts.partC.points.c2.y).to.equal(33) + expect(pattern.parts.partD.points.d1.x).to.equal(4) + expect(pattern.parts.partD.points.d1.y).to.equal(4) + expect(pattern.parts.partD.points.d2.x).to.equal(44) + expect(pattern.parts.partD.points.d2.y).to.equal(44) + // Paths in partA + expect(pattern.parts.partA.paths.a.ops[0].to.x).to.equal(1) + expect(pattern.parts.partA.paths.a.ops[0].to.y).to.equal(1) + expect(pattern.parts.partA.paths.a.ops[1].to.x).to.equal(11) + expect(pattern.parts.partA.paths.a.ops[1].to.y).to.equal(11) + // Paths in partB + expect(pattern.parts.partB.paths.a.ops[0].to.x).to.equal(1) + expect(pattern.parts.partB.paths.a.ops[0].to.y).to.equal(1) + expect(pattern.parts.partB.paths.a.ops[1].to.x).to.equal(11) + expect(pattern.parts.partB.paths.a.ops[1].to.y).to.equal(11) + expect(pattern.parts.partB.paths.b.ops[0].to.x).to.equal(2) + expect(pattern.parts.partB.paths.b.ops[0].to.y).to.equal(2) + expect(pattern.parts.partB.paths.b.ops[1].to.x).to.equal(22) + expect(pattern.parts.partB.paths.b.ops[1].to.y).to.equal(22) + // Paths in partC + expect(pattern.parts.partC.paths.a.ops[0].to.x).to.equal(1) + expect(pattern.parts.partC.paths.a.ops[0].to.y).to.equal(1) + expect(pattern.parts.partC.paths.a.ops[1].to.x).to.equal(11) + expect(pattern.parts.partC.paths.a.ops[1].to.y).to.equal(11) + expect(pattern.parts.partC.paths.b.ops[0].to.x).to.equal(2) + expect(pattern.parts.partC.paths.b.ops[0].to.y).to.equal(2) + expect(pattern.parts.partC.paths.b.ops[1].to.x).to.equal(22) + expect(pattern.parts.partC.paths.b.ops[1].to.y).to.equal(22) + expect(pattern.parts.partC.paths.c.ops[0].to.x).to.equal(3) + expect(pattern.parts.partC.paths.c.ops[0].to.y).to.equal(3) + expect(pattern.parts.partC.paths.c.ops[1].to.x).to.equal(33) + expect(pattern.parts.partC.paths.c.ops[1].to.y).to.equal(33) + // Paths in partR + expect(pattern.parts.partD.paths.d.ops[0].to.x).to.equal(4) + expect(pattern.parts.partD.paths.d.ops[0].to.y).to.equal(4) + expect(pattern.parts.partD.paths.d.ops[1].to.x).to.equal(44) + expect(pattern.parts.partD.paths.d.ops[1].to.y).to.equal(44) +})