1
0
Fork 0

implement and test final proposal

This commit is contained in:
Enoch Riese 2023-03-07 16:34:14 -06:00
parent f5c81d6767
commit ca80dfbfa3
3 changed files with 474 additions and 246 deletions

View file

@ -1,4 +1,4 @@
import { __addNonEnumProp } from './utils.mjs' import { __addNonEnumProp, capitalize } from './utils.mjs'
/** /**
* Get the name of the given plugin config * Get the name of the given plugin config
@ -46,12 +46,16 @@ export function PatternConfig(pattern) {
__addNonEnumProp(this, '__mutated', { __addNonEnumProp(this, '__mutated', {
optionDistance: {}, optionDistance: {},
partDistance: {}, partDistance: {},
hideDistance: {},
}) })
/** @type {Object} tracking for dependency hiding */ /** @type {Object} tracking for dependency hiding */
__addNonEnumProp(this, '__hiding', { __addNonEnumProp(this, '__hiding', {
all: {}, from: {},
deps: {}, after: {},
inherited: {},
always: {},
never: {},
}) })
} }
@ -133,14 +137,15 @@ PatternConfig.prototype.asConfig = function () {
PatternConfig.prototype.__addPart = function (depChain) { PatternConfig.prototype.__addPart = function (depChain) {
// the current part is the head of the chain // the current part is the head of the chain
const part = depChain[0] const part = depChain[0]
// the longer the chain, the deeper the part is down it
const distance = depChain.length // only process a part that hasn't already been processed
if (!this.parts[part.name]) this.parts[part.name] = Object.freeze(part) if (!this.parts[part.name]) this.parts[part.name] = Object.freeze(part)
else return else return
// if it hasn't been registered with a distance, do that now // if it hasn't been registered with a distance, do that now
if (typeof this.__mutated.partDistance[part.name] === 'undefined') { if (typeof this.__mutated.partDistance[part.name] === 'undefined') {
this.__mutated.partDistance[part.name] = distance // the longer the chain, the deeper the part is down it
this.__mutated.partDistance[part.name] = depChain.length
if (DISTANCE_DEBUG) if (DISTANCE_DEBUG)
this.store.log.debug( this.store.log.debug(
@ -149,11 +154,7 @@ PatternConfig.prototype.__addPart = function (depChain) {
} }
// Handle various hiding possibilities // Handle various hiding possibilities
if (part.hide || part.hideAll) this.partHide[part.name] = true this.__resolvePartHiding(part)
if (part.hideDependencies) this.__hiding.deps[part.name] = true
if (part.hideAll) {
this.__hiding.all[part.name] = true
}
// resolve its dependencies // resolve its dependencies
this.__resolvePartDependencies(depChain) this.__resolvePartDependencies(depChain)
@ -188,7 +189,7 @@ PatternConfig.prototype.__addPartOptions = function (part) {
if (!part.options) return this if (!part.options) return this
// get the part's option priority // get the part's option priority
const partDistance = this.__mutated.partDistance?.[part.name] || 0 const partDistance = this.__mutated.partDistance?.[part.name]
// loop through options // loop through options
for (const optionName in part.options) { for (const optionName in part.options) {
@ -325,7 +326,37 @@ PatternConfig.prototype.__addPartPlugins = function (part) {
// the two types of dependencies // the two types of dependencies
const depTypes = ['from', 'after'] const depTypes = ['from', 'after']
const exceptionTypes = ['never', 'always']
PatternConfig.prototype.__resolvePartHiding = function (part) {
if (part.hide) {
// get the part's option priority
const partDistance = this.__mutated.partDistance?.[part.name]
const neverDistance = this.__hiding.never[part.name] || Infinity
const alwaysDistance = this.__hiding.always[part.name] || Infinity
if (part.hide.self && (neverDistance > partDistance || alwaysDistance <= neverDistance))
this.partHide[part.name] = true
exceptionTypes.forEach((e, i) => {
if (part.hide[e]) {
part.hide[e].forEach((p) => {
const otherDistance = this.__hiding[exceptionTypes[Math.abs(i - 1)]][p] || Infinity
if (otherDistance > partDistance) {
const thisDistance = this.__hiding[e][p] || Infinity
this.__hiding[e][p] = Math.min(thisDistance, partDistance)
this.partHide[p] = i == 1
}
})
}
})
Object.keys(this.__hiding).forEach((k) => {
if (!exceptionTypes.includes(k) && this.__hiding[k][part.name] === undefined)
this.__hiding[k][part.name] = part.hide[k]
})
}
}
/** /**
* Recursively register part dependencies * Recursively register part dependencies
* triggers {@link __addPart} on new parts found during resolution * triggers {@link __addPart} on new parts found during resolution
@ -364,9 +395,11 @@ PatternConfig.prototype.__resolvePartDependencies = function (depChain) {
this.__addPart([dot, ...depChain]) this.__addPart([dot, ...depChain])
} else { } else {
// if it's already registered, recursion won't happen, but we still need to add its resolved dependencies to all parts in the chain // if it's already registered, recursion won't happen, but we still need to add its resolved dependencies to all parts in the chain
this.resolvedDependencies[dot.name].forEach((r) => { // this.resolvedDependencies[dot.name].forEach((r) => {
depChain.forEach((c) => this.__addDependency('resolvedDependencies', c.name, r)) // depChain.forEach((c) => this.__resolvePartDependencies('resolvedDependencies', c.name, r))
}) // })
this.__resolvePartDependencies([dot, ...depChain])
// and check for stricter hiding policies
} }
}) })
} }
@ -399,14 +432,18 @@ PatternConfig.prototype.__addDependency = function (dependencyList, partName, de
* @private * @private
*/ */
PatternConfig.prototype.__handlePartDependencyOfType = function (part, depName, depType) { PatternConfig.prototype.__handlePartDependencyOfType = function (part, depName, depType) {
switch (depType) { if (this.__hiding[depType][part.name] === true && this.partHide[depName] === undefined) {
case 'from': this.partHide[depName] = true
this.__setFromHide(part, depName)
this.inject[part.name] = depName
break
case 'after':
this.__setAfterHide(part, depName)
} }
const hideInherited = this.__hiding.inherited[part.name]
if (depType === 'from') {
this.inject[part.name] = depName
this.__hiding.after[depName] = hideInherited
}
this.__hiding.from[depName] = hideInherited
this.__hiding.inherited[depName] = hideInherited
} }
/** /**
@ -455,42 +492,3 @@ PatternConfig.prototype.__resolveDraftOrder = function () {
return this.__draftOrder return this.__draftOrder
} }
/**
* Sets visibility of a 'from' dependency based on its config
*
* @private
* @param {Part} part - The part of which this is a dependency
* @param {string} depName - The name of the dependency
* @return {Pattern} this - The Pattern instance
*/
PatternConfig.prototype.__setFromHide = function (part, depName) {
if (this.__hiding.deps[part.name]) {
this.partHide[depName] = true
this.__hiding.deps[depName] = true
}
if (this.__hiding.all[part.name]) {
this.partHide[depName] = true
this.__hiding.all[depName] = true
}
return this
}
/**
* Sets visibility of an 'after' dependency based on its config
*
* @private
* @param {Part} part - The part of which this is a dependency
* @param {string} depName - The name of the dependency
* @param {int} set - The index of the set in the list of settings
* @return {Pattern} this - The Pattern instance
*/
PatternConfig.prototype.__setAfterHide = function (part, depName) {
if (this.__hiding.all[part.name]) {
this.partHide[depName] = true
this.__hiding.all[depName] = true
}
return this
}

View file

@ -814,191 +814,6 @@ describe('Pattern', () => {
expect(count).to.equal(2) expect(count).to.equal(2)
}) })
describe('Hiding parts', () => {
const blankDraft = ({ part }) => part
const afterPart = {
name: 'afterPart',
draft: blankDraft,
}
const fromPart = {
name: 'fromPart',
draft: blankDraft,
}
describe('{hide: true}', () => {
const mainPart = {
name: 'mainPart',
after: afterPart,
from: fromPart,
hide: true,
draft: blankDraft,
}
const Test = new Design({
name: 'test',
parts: [mainPart],
})
const pattern = new Test()
pattern.__init()
it('Should hide the part', () => {
expect(pattern.__isPartHidden('mainPart')).to.be.true
})
it("Should not hide the part's dependencies", () => {
expect(pattern.__isPartHidden('fromPart')).to.be.false
expect(pattern.__isPartHidden('afterPart')).to.be.false
})
describe('Nested Parts', () => {
const mainPart = {
name: 'mainPart',
after: afterPart,
from: fromPart,
draft: blankDraft,
}
const grandChild = {
name: 'grandChild',
from: mainPart,
hide: true,
draft: blankDraft,
}
const Test = new Design({
name: 'test',
parts: [grandChild],
})
const pattern = new Test()
pattern.__init()
it('should not hide nested `from` dependencies', () => {
expect(pattern.__isPartHidden('fromPart')).to.be.false
expect(pattern.__isPartHidden('mainPart')).to.be.false
})
it('should not hide nested `after` dependencies', () => {
expect(pattern.__isPartHidden('afterPart')).to.be.false
})
})
})
describe('{hideDependencies: true}', () => {
const mainPart = {
name: 'mainPart',
hideDependencies: true,
after: afterPart,
from: fromPart,
draft: blankDraft,
}
const Test = new Design({
name: 'test',
parts: [mainPart],
})
const pattern = new Test()
pattern.__init()
it('Should not hide the part', () => {
expect(pattern.__isPartHidden('mainPart')).to.be.false
})
it("Should hide the part's `from` dependencies", () => {
expect(pattern.__isPartHidden('fromPart')).to.be.true
})
it("Should not hide the part's `after` dependencies", () => {
expect(pattern.__isPartHidden('afterPart')).to.be.false
})
describe('Nested Parts', () => {
const mainPart = {
name: 'mainPart',
after: afterPart,
from: fromPart,
draft: blankDraft,
}
const grandChild = {
name: 'grandChild',
from: mainPart,
hideDependencies: true,
draft: blankDraft,
}
const Test = new Design({
name: 'test',
parts: [grandChild],
})
const pattern = new Test()
pattern.__init()
it('should hide nested `from` dependencies', () => {
expect(pattern.__isPartHidden('fromPart')).to.be.true
expect(pattern.__isPartHidden('mainPart')).to.be.true
})
it('should not hide nested `after` dependencies', () => {
expect(pattern.__isPartHidden('afterPart')).to.be.false
})
})
})
describe('{hideAll: true}', () => {
const mainPart = {
name: 'mainPart',
hideAll: true,
after: afterPart,
from: fromPart,
draft: blankDraft,
}
const Test = new Design({
name: 'test',
parts: [mainPart],
})
const pattern = new Test()
pattern.__init()
it('Should hide the part', () => {
expect(pattern.__isPartHidden('mainPart')).to.be.true
})
it("Should hide the part's `from` dependencies", () => {
expect(pattern.__isPartHidden('fromPart')).to.be.true
})
it("Should hide the part's `after` dependencies", () => {
expect(pattern.__isPartHidden('afterPart')).to.be.true
})
describe('Nested Parts', () => {
const mainPart = {
name: 'mainPart',
after: afterPart,
from: fromPart,
draft: blankDraft,
}
const grandChild = {
name: 'grandChild',
from: mainPart,
hideAll: true,
draft: blankDraft,
}
const Test = new Design({
name: 'test',
parts: [grandChild],
})
const pattern = new Test()
pattern.__init()
it('should hide nested `from` dependencies', () => {
expect(pattern.__isPartHidden('fromPart')).to.be.true
expect(pattern.__isPartHidden('mainPart')).to.be.true
})
it('should hide nested `after` dependencies', () => {
expect(pattern.__isPartHidden('afterPart')).to.be.true
})
})
})
})
it('Should check whether created parts get the pattern context', () => { it('Should check whether created parts get the pattern context', () => {
let partContext let partContext
const plugin = { const plugin = {

View file

@ -0,0 +1,415 @@
import chai from 'chai'
import { Design } from '../src/index.mjs'
const expect = chai.expect
function hidePartMatcher(partName) {
const isHidden = this._obj.__isPartHidden(partName)
if (!this._obj.config.parts[partName]) {
throw new chai.AssertionError(`expected part \`${partName}\` to exist in pattern`)
this.fail()
}
this.assert(
isHidden,
`expected part ${partName} to be hidden, but it is shown`,
`expected part ${partName} to NOT be hidden, but it is hidden`
)
}
chai.Assertion.addMethod('hidePart', hidePartMatcher)
const blankDraft = ({ part }) => part
const blankPart = (name, config = {}) => ({
name,
draft: blankDraft,
...config,
})
describe('Hiding parts', () => {
const afterPart = blankPart('afterPart')
const fromPart = blankPart('fromPart')
describe('With {hide: {self: true}}', () => {
const mainPart = {
name: 'mainPart',
after: afterPart,
from: fromPart,
hide: { self: true },
draft: blankDraft,
}
const Test = new Design({
name: 'test',
parts: [mainPart],
})
const pattern = new Test()
pattern.__init()
it('Should hide the part', () => {
expect(pattern).to.hidePart('mainPart')
})
it("Should NOT hide the part's dependencies", () => {
expect(pattern).not.to.hidePart('fromPart')
expect(pattern).not.to.hidePart('afterPart')
})
describe('Inherited Parts', () => {
const mainPart = {
name: 'mainPart',
after: afterPart,
from: fromPart,
draft: blankDraft,
}
const grandChild = {
name: 'grandChild',
from: mainPart,
hide: { self: true },
draft: blankDraft,
}
const Test = new Design({
name: 'test',
parts: [grandChild],
})
const pattern = new Test()
pattern.__init()
it('Should NOT hide inherited `from` dependencies', () => {
expect(pattern).not.to.hidePart('fromPart')
expect(pattern).not.to.hidePart('mainPart')
})
it('Should NOT hide inherited `after` dependencies', () => {
expect(pattern).not.to.hidePart('afterPart')
})
})
})
describe('With {hide: {from: true}}', () => {
const mainPart = {
name: 'mainPart',
hide: { from: true },
after: afterPart,
from: fromPart,
draft: blankDraft,
}
const Test = new Design({
name: 'test',
parts: [mainPart],
})
const pattern = new Test()
pattern.__init()
it('Should NOT hide the part', () => {
expect(pattern).not.to.hidePart('mainPart')
})
it("Should hide the part's `from` dependencies", () => {
expect(pattern).to.hidePart('fromPart')
})
it("Should NOT hide the part's `after` dependencies", () => {
expect(pattern).not.to.hidePart('afterPart')
})
describe('Inherited Parts', () => {
const mainPart = {
name: 'mainPart',
after: afterPart,
from: fromPart,
draft: blankDraft,
}
const grandChild = {
name: 'grandChild',
from: mainPart,
hide: { from: true },
draft: blankDraft,
}
const Test = new Design({
name: 'test',
parts: [grandChild],
})
const pattern = new Test()
pattern.__init()
it("Should hide the part's `from` dependencies", () => {
expect(pattern).to.hidePart('mainPart')
})
it('Should NOT hide inherited `from` dependencies', () => {
expect(pattern).not.to.hidePart('fromPart')
})
it('Should NOT hide inherited `after` dependencies', () => {
expect(pattern).not.to.hidePart('afterPart')
})
})
})
describe('With {hide: {after: true}}', () => {
const mainPart = {
name: 'mainPart',
hide: { after: true },
after: afterPart,
from: fromPart,
draft: blankDraft,
}
const Test = new Design({
name: 'test',
parts: [mainPart],
})
const pattern = new Test()
pattern.__init()
it('Should NOT hide the part', () => {
expect(pattern).not.to.hidePart('mainPart')
})
it("Should NOT hide the part's `from` dependencies", () => {
expect(pattern).not.to.hidePart('fromPart')
})
it("Should hide the part's `after` dependencies", () => {
expect(pattern).to.hidePart('afterPart')
})
describe('Inherited Parts', () => {
const mainPart = {
name: 'mainPart',
after: afterPart,
from: fromPart,
draft: blankDraft,
}
const grandChild = {
name: 'grandChild',
from: mainPart,
hide: { after: true },
draft: blankDraft,
}
const Test = new Design({
name: 'test',
parts: [grandChild],
})
const pattern = new Test()
pattern.__init()
it('Should NOT hide inherited `from` dependencies', () => {
expect(pattern).not.to.hidePart('fromPart')
})
it('Should NOT hide inherited `after` dependencies', () => {
expect(pattern).not.to.hidePart('afterPart')
})
})
})
describe('With {hide: {inherited: true}}', () => {
const grandParent = blankPart('grandParent', { from: fromPart, after: afterPart })
const parentAfter = blankPart('parentAfter')
const parent = blankPart('parent', { from: grandParent, after: parentAfter })
const mainAfterFrom = blankPart('mainAfterFrom')
const mainAfterAfter = blankPart('mainAfterAfter')
const mainAfter = blankPart('mainAfter', { after: mainAfterAfter, from: mainAfterFrom })
const mainPart = {
name: 'mainPart',
from: parent,
after: mainAfter,
hide: { inherited: true },
draft: blankDraft,
}
const Test = new Design({
name: 'test',
parts: [mainPart],
})
const pattern = new Test()
pattern.__init()
it('Should NOT hide the part', () => {
expect(pattern).not.to.hidePart('mainPart')
})
it('Should NOT hide the `from` dependency', () => {
expect(pattern).not.to.hidePart('parent')
})
it('Should NOT hide the `after` dependency', () => {
expect(pattern).not.to.hidePart('mainAfter')
})
it('Should NOT hide the `after` dependencies of `after` dependencies', () => {
expect(pattern).not.to.hidePart('mainAfterAfter')
})
it('Should hide the `from` dependencies of `after` dependencies', () => {
expect(pattern).to.hidePart('mainAfterFrom')
})
it('Should hide the `after` dependencies of `from` dependencies', () => {
expect(pattern).to.hidePart('afterPart')
expect(pattern).to.hidePart('parentAfter')
})
it('Should hide the `from` dependencies of `from` dependencies', () => {
expect(pattern).to.hidePart('fromPart')
expect(pattern).to.hidePart('grandParent')
})
})
describe("With {hide: {always: ['partname']} }", () => {
it('Should hide the given part', () => {
const mainPart = blankPart('mainPart', {
after: afterPart,
hide: {
always: ['afterPart'],
},
})
const Test = new Design({
name: 'test',
parts: [mainPart],
})
const pattern = new Test()
pattern.__init()
expect(pattern).to.hidePart('afterPart')
})
it("Should NOT hide the given part if a higher-level part includes it in {hide: {never: ['partName']} }", () => {
const grandParent = blankPart('grandParent')
const parent = blankPart('parent', {
from: grandParent,
hide: { always: ['grandParent'] },
})
const main1 = blankPart('main1', {
from: parent,
hide: { from: true, never: ['grandParent'] },
})
const Test = new Design({
name: 'test',
parts: [main1],
})
const pattern = new Test()
pattern.__init()
expect(pattern).not.to.hidePart('grandParent')
})
})
describe("With {hide: {never: ['partName']} }", () => {
it('Should NOT hide the given part even if another setting would hide it', () => {
const parent = blankPart('parent')
const main1 = blankPart('main1', {
from: parent,
hide: { from: true, never: ['parent'] },
})
const Test = new Design({
name: 'test',
parts: [main1],
})
const pattern = new Test()
pattern.__init()
expect(pattern).not.to.hidePart('parent')
})
it("Should hide the given part if a higher-level part includes it in {hide: {always: ['partName']} }", () => {
const grandParent = blankPart('grandParent')
const parent = blankPart('parent', {
from: grandParent,
hide: { never: ['grandParent'] },
})
const main1 = blankPart('main1', {
from: parent,
hide: { from: true, always: ['grandParent'] },
})
const Test = new Design({
name: 'test',
parts: [main1],
})
const pattern = new Test()
pattern.__init()
expect(pattern).to.hidePart('grandParent')
})
})
describe('With complex inheritance', () => {
it('Should use the strictest hiding configuration given by toplevel parts', () => {
const greatGrandParent = blankPart('greatGrandParent')
const grandParent = blankPart('grandParent', { from: greatGrandParent })
const parent = blankPart('parent', { from: grandParent })
const main1 = blankPart('main1', {
from: parent,
})
const main2 = blankPart('main2', {
from: parent,
hide: { from: true, inherited: true },
})
const Test = new Design({
name: 'test',
parts: [main1, main2],
})
const pattern = new Test()
pattern.__init()
expect(pattern).to.hidePart('parent')
expect(pattern).to.hidePart('grandParent')
expect(pattern).to.hidePart('greatGrandParent')
})
it('Should use inherited configurations that are not overridden', () => {
const greatGrandParent = blankPart('greatGrandParent')
const grandParent = blankPart('grandParent', { from: greatGrandParent })
const parent = blankPart('parent', {
from: grandParent,
hide: { inherited: true },
})
const main1 = blankPart('main1', {
from: parent,
hide: { from: true },
})
const Test = new Design({
name: 'test',
parts: [main1],
})
const pattern = new Test()
pattern.__init()
expect(pattern).to.hidePart('greatGrandParent')
})
it('Should override inherited hiding configurations', () => {
const greatGrandParent = blankPart('greatGrandParent')
const grandParent = blankPart('grandParent', { from: greatGrandParent })
const parent = blankPart('parent', {
from: grandParent,
hide: { inherited: true },
})
const main1 = blankPart('main1', {
from: parent,
hide: { from: true, inherited: false },
})
const Test = new Design({
name: 'test',
parts: [main1],
})
const pattern = new Test()
pattern.__init()
expect(pattern).not.to.hidePart('greatGrandParent')
})
})
})