diff options
author | Florian Dold <florian.dold@gmail.com> | 2017-08-14 05:01:11 +0200 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2017-08-14 05:02:09 +0200 |
commit | 363723fc84f7b8477592e0105aeb331ec9a017af (patch) | |
tree | 29f92724f34131bac64d6a318dd7e30612e631c7 /node_modules/@concordance/react/lib | |
parent | 5634e77ad96bfe1818f6b6ee70b7379652e5487f (diff) |
node_modules
Diffstat (limited to 'node_modules/@concordance/react/lib')
-rw-r--r-- | node_modules/@concordance/react/lib/diffShallow.js | 239 | ||||
-rw-r--r-- | node_modules/@concordance/react/lib/elementFactory.js | 349 | ||||
-rw-r--r-- | node_modules/@concordance/react/lib/escapeText.js | 10 | ||||
-rw-r--r-- | node_modules/@concordance/react/lib/testJsonFactory.js | 59 |
4 files changed, 657 insertions, 0 deletions
diff --git a/node_modules/@concordance/react/lib/diffShallow.js b/node_modules/@concordance/react/lib/diffShallow.js new file mode 100644 index 000000000..4eeb4d4d3 --- /dev/null +++ b/node_modules/@concordance/react/lib/diffShallow.js @@ -0,0 +1,239 @@ +'use strict' + +function diffShallow (api, actual, expected, theme, indent) { + const childBuffer = api.lineBuilder.buffer() + const propertyBuffer = api.lineBuilder.buffer() + + return { + append (formatted, origin) { + if (origin.isItem === true) { + childBuffer.append(formatted) + } else { + propertyBuffer.append(formatted) + } + }, + + finalize: () => { + const namesAreEqual = actual.compareNames(expected) + const actualName = actual.formatName(theme) + const expectedName = expected.formatName(theme) + + const openTag = theme.react.openTag + const innerIndentation = indent.increase() + + const allChildren = childBuffer.withFirstPrefixed(innerIndentation) + const children = allChildren.decompose() + + const allProperties = propertyBuffer.withFirstPrefixed(innerIndentation) + const properties = allProperties.decompose() + // If the first properties are also the last, and either side has no + // children, ensure the properties are treated as being last. This + // leads to a better balanced diff. + if (properties.remaining.isEmpty && (!actual.hasChildren || !expected.hasChildren)) { + properties.last = properties.first + properties.first = {actual: api.lineBuilder.buffer(), expected: api.lineBuilder.buffer()} + } + + const result = api.lineBuilder.buffer() + + // Create a custom diff that is as neat as possible. It's likely + // there's a generic algorithm that can be used, but for expediency's + // sake handles all possible diffs by brute force instead. + if (actual.hasProperties && expected.hasProperties) { + if (namesAreEqual) { + result + .append(api.lineBuilder.first(openTag.start + actualName)) + .append(properties.first.actual.stripFlags()) + .append(properties.first.expected.stripFlags()) + } else { + result + .append(api.lineBuilder.actual.first(openTag.start + actualName)) + .append(properties.first.actual.stripFlags()) + .append(api.lineBuilder.expected.first(openTag.start + expectedName)) + .append(properties.first.expected.stripFlags()) + } + result.append(properties.remaining.stripFlags()) + + if (actual.hasChildren && expected.hasChildren) { + result + .append(properties.last.actual.stripFlags()) + .append(properties.last.expected.stripFlags()) + .append(api.lineBuilder.line(indent + openTag.end)) + + if (namesAreEqual) { + result + .append(allChildren.stripFlags()) + .append(api.lineBuilder.last(indent + api.wrapFromTheme(theme.react.closeTag, actualName))) + } else { + result + .append(children.first.actual.stripFlags()) + .append(children.first.expected.stripFlags()) + .append(children.remaining.stripFlags()) + .append(children.last.actual.stripFlags()) + .append(api.lineBuilder.actual.last(indent + api.wrapFromTheme(theme.react.closeTag, actualName))) + .append(children.last.expected.stripFlags()) + .append(api.lineBuilder.expected.last(indent + api.wrapFromTheme(theme.react.closeTag, expectedName))) + } + } else if (actual.hasChildren) { + result + .append(properties.last.actual.stripFlags()) + .append(api.lineBuilder.actual.line(indent + openTag.end)) + .append(allChildren.stripFlags()) + .append(api.lineBuilder.actual.last(indent + api.wrapFromTheme(theme.react.closeTag, actualName))) + .append(properties.last.expected.stripFlags()) + .append(api.lineBuilder.expected.last(indent + openTag.selfClose + openTag.end)) + } else if (expected.hasChildren) { + result + .append(properties.last.actual.stripFlags()) + .append(api.lineBuilder.actual.last(indent + openTag.selfClose + openTag.end)) + .append(properties.last.expected.stripFlags()) + .append(api.lineBuilder.expected.line(indent + openTag.end)) + .append(allChildren.stripFlags()) + .append(api.lineBuilder.expected.last(indent + api.wrapFromTheme(theme.react.closeTag, expectedName))) + } else { + result + .append(properties.last.actual.stripFlags()) + .append(properties.last.expected.stripFlags()) + .append(api.lineBuilder.last(indent + openTag.selfClose + openTag.end)) + } + } else if (actual.hasProperties) { + result + .append(api.lineBuilder.actual.first(openTag.start + actualName)) + .append(allProperties.stripFlags()) + + if (actual.hasChildren && expected.hasChildren) { + result + .append(api.lineBuilder.actual.line(indent + openTag.end)) + .append(children.first.actual.stripFlags()) + .append(api.lineBuilder.expected.first(openTag.start + expectedName + openTag.end)) + .append(children.first.expected.stripFlags()) + .append(children.remaining.stripFlags()) + + if (namesAreEqual) { + result + .append(children.last.actual.stripFlags()) + .append(children.last.expected.stripFlags()) + .append(api.lineBuilder.last(indent + api.wrapFromTheme(theme.react.closeTag, actualName))) + } else { + result + .append(children.last.actual.stripFlags()) + .append(api.lineBuilder.actual.last(indent + api.wrapFromTheme(theme.react.closeTag, actualName))) + .append(children.last.expected.stripFlags()) + .append(api.lineBuilder.expected.last(indent + api.wrapFromTheme(theme.react.closeTag, expectedName))) + } + } else if (actual.hasChildren) { + result + .append(api.lineBuilder.actual.last(indent + openTag.selfClose + openTag.end)) + .append(allChildren.stripFlags()) + .append(api.lineBuilder.actual.last(indent + api.wrapFromTheme(theme.react.closeTag, actualName))) + .append(api.lineBuilder.expected.single(openTag.start + expectedName + openTag.selfCloseVoid + openTag.end)) + } else if (expected.hasChildren) { + result + .append(api.lineBuilder.actual.last(indent + openTag.selfClose + openTag.end)) + .append(api.lineBuilder.expected.first(openTag.start + expectedName + openTag.end)) + .append(allChildren.stripFlags()) + .append(api.lineBuilder.expected.last(indent + api.wrapFromTheme(theme.react.closeTag, expectedName))) + } else { + result + .append(api.lineBuilder.actual.last(indent + openTag.selfClose + openTag.end)) + .append(api.lineBuilder.expected.single(openTag.start + expectedName + openTag.selfCloseVoid + openTag.end)) + } + } else if (expected.hasProperties) { + if (actual.hasChildren && expected.hasChildren) { + result + .append(api.lineBuilder.actual.first(openTag.start + actualName + openTag.end)) + .append(children.first.actual.stripFlags()) + .append(api.lineBuilder.expected.first(openTag.start + expectedName)) + .append(allProperties.stripFlags()) + .append(api.lineBuilder.expected.line(indent + openTag.end)) + .append(children.first.expected.stripFlags()) + .append(children.remaining.stripFlags()) + + if (namesAreEqual) { + result + .append(children.last.actual.stripFlags()) + .append(children.last.expected.stripFlags()) + .append(api.lineBuilder.last(indent + api.wrapFromTheme(theme.react.closeTag, actualName))) + } else { + result + .append(children.last.actual.stripFlags()) + .append(api.lineBuilder.actual.last(indent + api.wrapFromTheme(theme.react.closeTag, actualName))) + .append(children.last.expected.stripFlags()) + .append(api.lineBuilder.expected.last(indent + api.wrapFromTheme(theme.react.closeTag, expectedName))) + } + } else if (actual.hasChildren) { + result + .append(api.lineBuilder.actual.first(openTag.start + actualName + openTag.end)) + .append(allChildren.stripFlags()) + .append(api.lineBuilder.actual.last(indent + api.wrapFromTheme(theme.react.closeTag, actualName))) + .append(api.lineBuilder.expected.first(openTag.start + expectedName)) + .append(allProperties.stripFlags()) + .append(api.lineBuilder.expected.last(indent + openTag.selfClose + openTag.end)) + } else if (expected.hasChildren) { + result + .append(api.lineBuilder.actual.single(openTag.start + actualName + openTag.selfCloseVoid + openTag.end)) + .append(api.lineBuilder.expected.first(openTag.start + expectedName)) + .append(allProperties.stripFlags()) + .append(api.lineBuilder.expected.line(indent + openTag.end)) + .append(allChildren.stripFlags()) + .append(api.lineBuilder.expected.last(indent + api.wrapFromTheme(theme.react.closeTag, expectedName))) + } else { + result + .append(api.lineBuilder.actual.single(openTag.start + actualName + openTag.selfCloseVoid + openTag.end)) + .append(api.lineBuilder.expected.first(openTag.start + expectedName)) + .append(allProperties.stripFlags()) + .append(api.lineBuilder.expected.last(indent + openTag.selfCloseVoid + openTag.end)) + } + } else { + if (actual.hasChildren && expected.hasChildren) { + if (namesAreEqual) { + result + .append(api.lineBuilder.first(openTag.start + actualName + openTag.end)) + .append(allChildren.stripFlags()) + .append(api.lineBuilder.last(indent + api.wrapFromTheme(theme.react.closeTag, actualName))) + } else { + result + .append(api.lineBuilder.actual.first(openTag.start + actualName + openTag.end)) + .append(children.first.actual.stripFlags()) + .append(api.lineBuilder.expected.first(openTag.start + expectedName + openTag.end)) + .append(children.first.expected.stripFlags()) + .append(children.remaining.stripFlags()) + .append(children.last.actual.stripFlags()) + .append(api.lineBuilder.actual.last(indent + api.wrapFromTheme(theme.react.closeTag, actualName))) + .append(children.last.expected.stripFlags()) + .append(api.lineBuilder.expected.last(indent + api.wrapFromTheme(theme.react.closeTag, expectedName))) + } + } else if (actual.hasChildren) { + result + .append(api.lineBuilder.actual.first(openTag.start + actualName + openTag.end)) + .append(allChildren.stripFlags()) + .append(api.lineBuilder.actual.last(indent + api.wrapFromTheme(theme.react.closeTag, actualName))) + .append(api.lineBuilder.expected.single(openTag.start + expectedName + openTag.selfCloseVoid + openTag.end)) + } else if (expected.hasChildren) { + result + .append(api.lineBuilder.actual.single(openTag.start + actualName + openTag.selfCloseVoid + openTag.end)) + .append(api.lineBuilder.expected.first(openTag.start + expectedName + openTag.end)) + .append(allChildren.stripFlags()) + .append(api.lineBuilder.expected.last(indent + api.wrapFromTheme(theme.react.closeTag, actualName))) + } else { + if (namesAreEqual) { + result.append(api.lineBuilder.single(openTag.start + actualName + openTag.selfCloseVoid + openTag.end)) + } else { + result + .append(api.lineBuilder.actual.single(openTag.start + actualName + openTag.selfCloseVoid + openTag.end)) + .append(api.lineBuilder.expected.single(openTag.start + expectedName + openTag.selfCloseVoid + openTag.end)) + } + } + } + + return result + }, + + shouldFormat (subject) { + return subject.isItem === true || subject.isProperty === true + }, + + increaseIndent: true + } +} +module.exports = diffShallow diff --git a/node_modules/@concordance/react/lib/elementFactory.js b/node_modules/@concordance/react/lib/elementFactory.js new file mode 100644 index 000000000..9be125cef --- /dev/null +++ b/node_modules/@concordance/react/lib/elementFactory.js @@ -0,0 +1,349 @@ +'use strict' + +const arrify = require('arrify') +const diffShallow = require('./diffShallow') +const escapeText = require('./escapeText') + +function factory (api, reactTags) { + const tag = Symbol('@concordance/react.ElementValue') + + function customPropertyFormatter (theme, indent, key, value) { + const separator = theme.react.attribute.separator + theme.react.attribute.value.openBracket + if (value.isSingle) { + return value + .withFirstPrefixed(key.formatAsKey(theme) + separator) + .withLastPostfixed(theme.react.attribute.value.closeBracket) + } + + return api.lineBuilder.first(key.formatAsKey(theme) + separator) + .concat(value.withFirstPrefixed(indent.increase()).stripFlags()) + .append(api.lineBuilder.last(indent + theme.react.attribute.value.closeBracket)) + } + + function themeProperty (theme) { + theme.property.increaseValueIndent = true + theme.property.customFormat = customPropertyFormatter + } + + function themeStringProperty (theme) { + theme.property.separator = theme.react.attribute.separator + theme.property.after = '' + Object.assign(theme.string.line, theme.react.attribute.value.string.line) + } + + function customItemFormatter (theme, indent, value) { + if (value.isSingle) { + return value + .withFirstPrefixed(theme.react.child.openBracket) + .withLastPostfixed(theme.react.child.closeBracket) + } + + return api.lineBuilder.first(theme.react.child.openBracket) + .concat(value.withFirstPrefixed(indent.increase()).stripFlags()) + .append(api.lineBuilder.last(indent + theme.react.child.closeBracket)) + } + + function themeChild (theme) { + theme.item.increaseValueIndent = true + theme.item.customFormat = customItemFormatter + } + + function themeReactChild (theme) { + theme.item.after = '' + } + + function themeStringChild (theme) { + theme.item.after = '' + Object.assign(theme.string, theme.react.child.string) + } + + function describe (props) { + const element = props.value + + const type = element.type + const hasTypeFn = typeof type === 'function' + const typeFn = hasTypeFn ? type : null + const name = hasTypeFn ? type.displayName || type.name : type + + const children = arrify(element.props.children) + + const properties = Object.assign({}, element.props) + delete properties.children + if (element.key !== null) { + properties.key = element.key + } + const hasProperties = Object.keys(properties).length > 0 + + return new DescribedElementValue(Object.assign({ + children, + hasProperties, + hasTypeFn, + name, + properties, + typeFn, + isList: children.length > 0 + }, props)) + } + + function deserialize (state, recursor) { + return new DeserializedElementValue(state, recursor) + } + + class ElementValue extends api.ObjectValue { + constructor (props) { + super(props) + this.name = props.name + this.hasProperties = props.hasProperties + this.hasTypeFn = props.hasTypeFn + + this.hasChildren = this.isList + } + + compare (expected) { + return this.tag === expected.tag && this.name === expected.name + ? api.SHALLOW_EQUAL + : api.UNEQUAL + } + + formatName (theme) { + const formatted = api.wrapFromTheme(theme.react.tagName, this.name) + return this.hasTypeFn + ? formatted + theme.react.functionType + : formatted + } + + compareNames (expected) { + return this.name === expected.name && this.hasTypeFn === expected.hasTypeFn + } + + formatShallow (theme, indent) { + const childBuffer = api.lineBuilder.buffer() + const propertyBuffer = api.lineBuilder.buffer() + + return { + append (formatted, origin) { + if (origin.isItem === true) { + childBuffer.append(formatted) + } else { + propertyBuffer.append(formatted) + } + }, + + finalize: () => { + const name = this.formatName(theme) + const openTag = theme.react.openTag + + if (!this.hasChildren && !this.hasProperties) { + return api.lineBuilder.single(openTag.start + name + openTag.selfCloseVoid + openTag.end) + } + + const innerIndentation = indent.increase() + const children = childBuffer.withFirstPrefixed(innerIndentation).stripFlags() + const properties = propertyBuffer.withFirstPrefixed(innerIndentation).stripFlags() + + const result = api.lineBuilder.buffer() + if (this.hasProperties) { + result + .append(api.lineBuilder.first(openTag.start + name)) + .append(properties) + + if (this.hasChildren) { + result.append(api.lineBuilder.line(indent + openTag.end)) + } else { + result.append(api.lineBuilder.last(indent + openTag.selfClose + openTag.end)) + } + } else { + result.append(api.lineBuilder.first(openTag.start + name + openTag.end)) + } + + if (this.hasChildren) { + result + .append(children) + .append(api.lineBuilder.last(indent + api.wrapFromTheme(theme.react.closeTag, name))) + } + + return result + }, + + maxDepth: () => { + const name = this.formatName(theme) + const openTag = theme.react.openTag + + if (!this.hasChildren && !this.hasProperties) { + return api.lineBuilder.single(openTag.start + name + openTag.selfCloseVoid + openTag.end) + } + + let str = openTag.start + name + if (this.hasProperties) { + str += theme.maxDepth + if (this.hasChildren) { + str += openTag.end + } else { + str += ' ' + openTag.selfClose + openTag.end + } + } else { + str += openTag.end + } + + if (this.hasChildren) { + str += theme.maxDepth + api.wrapFromTheme(theme.react.closeTag, name) + } + + return api.lineBuilder.single(str) + }, + + shouldFormat (subject) { + return subject.isItem === true || subject.isProperty === true + }, + + increaseIndent: true + } + } + + prepareDiff (expected) { + return { + compareResult: this.tag === expected.tag + ? api.SHALLOW_EQUAL + : api.UNEQUAL + } + } + + diffShallow (expected, theme, indent) { + return diffShallow(api, this, expected, theme, indent) + } + + serialize () { + return [this.name, this.hasProperties, this.hasTypeFn, super.serialize()] + } + } + Object.defineProperty(ElementValue.prototype, 'tag', {value: tag}) + + function modifyThemes (recursor) { + return api.mapRecursor(recursor, next => { + let modifier + if (next.isItem === true) { + if (next.tag === api.descriptorTags.primitiveItem && next.value.tag === api.descriptorTags.string) { + modifier = themeStringChild + } else if (next.tag === api.descriptorTags.complexItem && reactTags.has(next.value.tag)) { + modifier = themeReactChild + } else { + modifier = themeChild + } + } else if (next.isProperty === true) { + if ( + next.tag === api.descriptorTags.primitiveProperty && + next.value.tag === api.descriptorTags.string && + !next.value.includesLinebreaks + ) { + modifier = themeStringProperty + } else { + modifier = themeProperty + } + } + + return modifier + ? api.modifyTheme(next, modifier) + : next + }) + } + + function DescribedMixin (base) { + return class extends api.DescribedMixin(base) { + constructor (props) { + super(props) + this.children = props.children + this.properties = props.properties + this.typeFn = props.typeFn + } + + compare (expected) { + const result = super.compare(expected) + return result === api.SHALLOW_EQUAL && this.typeFn !== expected.typeFn + ? api.UNEQUAL + : result + } + + compareNames (expected) { + return super.compareNames(expected) && this.typeFn === expected.typeFn + } + + createPropertyRecursor () { + // Symbols are not valid property keys for React elements. This code + // also assumes that the keys can be formatted as JSX-like attribute + // names. Keys are not pre-escaped before being passed to Concordance's + // property descriptor. + const keys = Object.keys(this.properties).sort() + const size = keys.length + + let index = 0 + const next = () => { + if (index === size) return null + + const key = keys[index++] + // Note that string values are not specifically escaped such that the + // output is valid JSX. + return this.describeProperty(key, this.describeAny(this.properties[key])) + } + + return {size, next} + } + + createListRecursor () { + if (!this.isList) return super.createListRecursor() + + const size = this.children.length + + let index = 0 + const next = () => { + if (index === size) return null + + const current = index++ + const child = this.children[current] + const type = typeof child + let descriptor + if (type === 'string') { + descriptor = this.describeAny(escapeText(child)) + } else { + descriptor = this.describeAny(child) + } + + return this.describeItem(current, descriptor) + } + + return {size, next} + } + + createRecursor () { + return modifyThemes(super.createRecursor()) + } + } + } + + function DeserializedMixin (base) { + return class extends api.DeserializedMixin(base) { + constructor (state, recursor) { + super(state[3], recursor) + this.name = state[0] + this.hasProperties = state[1] + this.hasTypeFn = state[2] + } + + createRecursor () { + return modifyThemes(super.createRecursor()) + } + } + } + + const DescribedElementValue = DescribedMixin(ElementValue) + const DeserializedElementValue = DeserializedMixin(ElementValue) + + return { + DescribedMixin, + DeserializedMixin, + ElementValue, + describe, + deserialize, + tag + } +} +module.exports = factory diff --git a/node_modules/@concordance/react/lib/escapeText.js b/node_modules/@concordance/react/lib/escapeText.js new file mode 100644 index 000000000..52447b1b6 --- /dev/null +++ b/node_modules/@concordance/react/lib/escapeText.js @@ -0,0 +1,10 @@ +'use strict' + +function escapeText (text) { + return text + .replace(/</g, '<') + .replace(/>/g, '>') + // TODO: Escape characters that Concordance would otherwise replace with \u + // sequences. +} +module.exports = escapeText diff --git a/node_modules/@concordance/react/lib/testJsonFactory.js b/node_modules/@concordance/react/lib/testJsonFactory.js new file mode 100644 index 000000000..c3e97a620 --- /dev/null +++ b/node_modules/@concordance/react/lib/testJsonFactory.js @@ -0,0 +1,59 @@ +'use strict' + +const arrify = require('arrify') + +function factory (api, element) { + const tag = Symbol('@concordance/react.TestJsonValue') + + function describe (props) { + const obj = props.value + + const name = obj.type + const children = arrify(obj.children) + const properties = Object.assign({}, obj.props) + const hasProperties = Object.keys(properties).length > 0 + + return new DescribedTestJsonValue(Object.assign({ + children, + hasProperties, + hasTypeFn: false, + name, + properties, + typeFn: null, + isList: children.length > 0 + }, props)) + } + + function deserialize (state, recursor) { + return new DeserializedTestJsonValue(state, recursor) + } + + class TestJsonValue extends element.ElementValue { + compare (expected) { + // Allow expected value to be a React element. + return (this.tag === expected.tag || expected.tag === element.tag) && this.name === expected.name + ? api.SHALLOW_EQUAL + : api.UNEQUAL + } + + prepareDiff (expected) { + return { + // Allow expected value to be a React element. + compareResult: this.tag === expected.tag || expected.tag === element.tag + ? api.SHALLOW_EQUAL + : api.UNEQUAL + } + } + } + Object.defineProperty(TestJsonValue.prototype, 'tag', {value: tag}) + + const DescribedTestJsonValue = element.DescribedMixin(TestJsonValue) + const DeserializedTestJsonValue = element.DeserializedMixin(TestJsonValue) + + return { + describe, + deserialize, + tag + } +} +module.exports = factory |