From baef518b13073f59cf2f76eee3cd3bdc3c1b3a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christophe=20No=C3=ABl?= Date: Thu, 22 Jul 2021 09:05:42 +0200 Subject: [PATCH 01/11] prettify code --- .eslintrc.js | 88 +- .idea/prettier.xml | 7 + .prettierignore | 6 + .prettierrc | 8 + README.md | 5 +- jest.config.js | 24 +- package-lock.json | 6 + package.json | 2 + src/core/addEventEmitter.js | 2 +- src/core/dom/htmlToElement.js | 2 +- .../exceptions/IncorrectBeatCountException.js | 30 +- .../InvalidChordRepetitionException.js | 12 +- src/parser/getAllChordsInSong.js | 6 +- src/parser/helper/clearSpaces.js | 7 +- src/parser/helper/songs.js | 19 +- src/parser/matchers/isChord.js | 4 +- src/parser/matchers/isChordLine.js | 13 +- src/parser/matchers/isChordLineRepeater.js | 2 +- src/parser/matchers/isEmptyLine.js | 2 +- src/parser/matchers/isSectionLabel.js | 10 +- src/parser/matchers/isTimeSignatureString.js | 13 +- src/parser/parseChordLine.js | 35 +- src/parser/parseSectionLabel.js | 9 +- src/parser/parseSong.js | 9 +- src/parser/parseTextLine.js | 6 + src/parser/parseTimeSignature.js | 6 +- src/parser/songLinesFactory.js | 59 +- src/renderer/components/renderBarContent.js | 8 +- src/renderer/components/renderChordLine.js | 10 +- src/renderer/components/renderChordSymbol.js | 2 +- src/renderer/components/renderLine.js | 13 +- src/renderer/components/renderSectionLabel.js | 12 +- src/renderer/components/renderSong.js | 55 +- src/renderer/helpers/getChordSymbol.js | 4 +- src/renderer/helpers/getMainAccidental.js | 6 +- src/renderer/helpers/getSectionsStats.js | 5 +- src/renderer/spacers/chord/aligned.js | 13 +- .../spacers/chord/getMaxBeatsWidth.js | 6 +- src/renderer/spacers/chord/simple.js | 41 +- tests/.eslintrc.js | 22 +- tests/integration/parser/parseSong.spec.js | 86 +- .../components/renderSong/renderSong.spec.js | 35 +- tests/unit/core/addEventEmitter.spec.js | 21 +- tests/unit/core/dom/htmlToElement.spec.js | 7 +- tests/unit/core/dom/stripTags.spec.js | 7 +- .../IncorrectBeatCountExpection.spec.js | 40 +- .../InvalidChordRepetitionException.spec.js | 20 +- tests/unit/parser/helpers/clearSpaces.spec.js | 23 +- tests/unit/parser/helpers/song.spec.js | 58 +- tests/unit/parser/matchers/isChord.spec.js | 32 +- .../unit/parser/matchers/isChordLine.spec.js | 54 +- .../matchers/isChordLineRepeater.spec.js | 14 +- .../unit/parser/matchers/isEmptyLine.spec.js | 18 +- .../parser/matchers/isSectionLabel.spec.js | 52 +- .../matchers/isTimeSignatureString.spec.js | 26 +- tests/unit/parser/parseChordLine.spec.js | 1058 +++++++++++------ tests/unit/parser/parseSectionLabel.spec.js | 25 +- tests/unit/parser/parseSong.spec.js | 37 +- tests/unit/parser/parseTimeSignature.spec.js | 47 +- tests/unit/parser/songLinesFactory.spec.js | 721 ++++++++--- .../components/renderBarContent.spec.js | 78 +- .../components/renderChordLine.spec.js | 11 +- .../components/renderChordSymbol.spec.js | 6 +- .../components/renderEmptyLine.spec.js | 1 - .../renderer/components/renderLine.spec.js | 58 +- .../components/renderSectionLabel.spec.js | 172 +-- .../renderer/components/renderSong.spec.js | 49 +- .../renderer/helpers/getChordSymbol.spec.js | 4 +- .../helpers/getMainAccidental.spec.js | 20 +- .../renderer/helpers/getSectionsStats.spec.js | 2 +- .../renderer/spacers/chord/aligned.spec.js | 88 +- .../spacers/chord/getMaxBeatsWidth.spec.js | 115 +- .../renderer/spacers/chord/simple.spec.js | 48 +- todo.md | 6 +- webpack.config.js | 18 +- 75 files changed, 2233 insertions(+), 1413 deletions(-) create mode 100644 .idea/prettier.xml create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 src/parser/parseTextLine.js diff --git a/.eslintrc.js b/.eslintrc.js index 1a5243ab..9d9e8f1a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,63 +1,65 @@ module.exports = { - 'env': { - 'browser': true, - 'es6': true + env: { + browser: true, + es6: true, }, - 'extends': [ - 'eslint:recommended' - ], + extends: ['eslint:recommended'], - 'plugins': [ - 'import', - 'no-unsanitized' - ], + plugins: ['import', 'no-unsanitized'], - 'globals': { - 'Atomics': 'readonly', - 'SharedArrayBuffer': 'readonly' + globals: { + Atomics: 'readonly', + SharedArrayBuffer: 'readonly', }, - 'parserOptions': { - 'ecmaVersion': 2018, - 'sourceType': 'module' + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', }, - 'rules': { - 'complexity': [ 'error', { max: 10 } ], - 'indent': [ 'error', 'tab', { 'SwitchCase': 1 } ], - 'linebreak-style': [ 'error', 'unix' ], - 'max-depth': [ 'error', 4 ], - 'max-len': [ 'error', {'code': 150 } ], - 'max-lines': [ 'error', { max: 300, skipBlankLines: true, skipComments: true, } ], - 'max-lines-per-function': [ 'warn', { max: 50, skipBlankLines: true, skipComments: true, }], - 'max-params': [ 'warn', { max: 4 } ], - 'no-shadow': [ 'error', { 'builtinGlobals': true } ], - 'quotes': [ 'error', 'single' ], - 'semi': [ 'error', 'always'], - - 'no-restricted-imports': [ + rules: { + complexity: ['error', { max: 10 }], + indent: ['error', 'tab', { SwitchCase: 1 }], + 'linebreak-style': ['error', 'unix'], + 'max-depth': ['error', 4], + 'max-len': ['error', { code: 150 }], + 'max-lines': [ + 'error', + { max: 300, skipBlankLines: true, skipComments: true }, + ], + 'max-lines-per-function': [ + 'warn', + { max: 50, skipBlankLines: true, skipComments: true }, + ], + 'max-params': ['warn', { max: 4 }], + 'no-shadow': ['error', { builtinGlobals: true }], + semi: ['error', 'always'], + + 'no-restricted-imports': [ 'error', { paths: [ { name: 'lodash', - message: 'Please do not import lodash as a whole: import individual lodash functions instead.' - } - ] - } + message: + 'Please do not import lodash as a whole: import individual lodash functions instead.', + }, + ], + }, ], - 'no-unsanitized/property': [ 'error', { escape: { methods: ['escapeHTML'] } } ], - 'no-unsanitized/method': [ 'error' ], - + 'no-unsanitized/property': [ + 'error', + { escape: { methods: ['escapeHTML'] } }, + ], + 'no-unsanitized/method': ['error'], - 'import/no-restricted-paths': [ 'error', + 'import/no-restricted-paths': [ + 'error', { - 'zones': [ - { 'target': './src/parser', 'from': './src/renderer' }, - ] - } + zones: [{ target: './src/parser', from: './src/renderer' }], + }, ], - } + }, }; diff --git a/.idea/prettier.xml b/.idea/prettier.xml new file mode 100644 index 00000000..9c8fc053 --- /dev/null +++ b/.idea/prettier.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..145ce011 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +coverage +CHANGELOG.md +lib +package.json +package-lock.json +SLOC diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..c742e225 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "endOfLine": "lf", + "semi": true, + "singleQuote": true, + "tabWidth": 4, + "trailingComma": "es5", + "useTabs": true +} diff --git a/README.md b/README.md index 783f3baf..41c0adaa 100644 --- a/README.md +++ b/README.md @@ -7,5 +7,6 @@ The best way to write chords charts Finally chords charts that are: -- easy to write -- complete and accurate, with bar information + +- easy to write +- complete and accurate, with bar information diff --git a/jest.config.js b/jest.config.js index f3a801bc..226d5b5f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,22 +1,19 @@ /* eslint-env node */ module.exports = { testEnvironment: 'jsdom', - + collectCoverage: true, - collectCoverageFrom: [ - 'src/**/*.js', - '!**/node_modules/**' - ], + collectCoverageFrom: ['src/**/*.js', '!**/node_modules/**'], coverageDirectory: '/coverage', coveragePathIgnorePatterns: ['node_modules'], coverageReporters: ['json', 'lcov', 'text', 'clover'], coverageThreshold: { - 'global': { - 'branches': 99, - 'functions': 100, - 'lines': 100, - 'statements': 0 - } + global: { + branches: 99, + functions: 100, + lines: 100, + statements: 0, + }, }, transform: { @@ -25,7 +22,6 @@ module.exports = { }, moduleNameMapper: { - '\\.(css|scss)$': '/scss/__mocks__/styleMock.js' - } - + '\\.(css|scss)$': '/scss/__mocks__/styleMock.js', + }, }; diff --git a/package-lock.json b/package-lock.json index 1aca5314..cb8c024c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10308,6 +10308,12 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", + "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", + "dev": true + }, "pretty-format": { "version": "27.0.6", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", diff --git a/package.json b/package.json index fd357388..3b609288 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "jest-handlebars": "^1.0.1", "mini-css-extract-plugin": "^2.0.0", "node-sass": "^6.0.1", + "prettier": "^2.3.2", "raw-loader": "^4.0.2", "sass": "^1.32.13", "sass-loader": "^12.1.0", @@ -53,6 +54,7 @@ "tiny-emitter": "^2.1.0" }, "scripts": { + "format": "prettier --write \"**/*.{js,jsx,json,ts,md}\"", "lint": "eslint src tests", "test": "jest", "bundle-js": "webpack", diff --git a/src/core/addEventEmitter.js b/src/core/addEventEmitter.js index 29fb174f..d0a4a1e7 100644 --- a/src/core/addEventEmitter.js +++ b/src/core/addEventEmitter.js @@ -18,7 +18,7 @@ export default function addEventEmitter(object) { emit(eventName, ...args) { this._emitter.emit(eventName, ...args); - } + }, }; return Object.assign(object, eventEmitter); diff --git a/src/core/dom/htmlToElement.js b/src/core/dom/htmlToElement.js index a54c54d8..104299e2 100644 --- a/src/core/dom/htmlToElement.js +++ b/src/core/dom/htmlToElement.js @@ -2,6 +2,6 @@ import domPurify from 'dompurify'; export default function htmlToElement(html) { return domPurify.sanitize(html, { - RETURN_DOM_FRAGMENT: true + RETURN_DOM_FRAGMENT: true, }).firstChild; } diff --git a/src/parser/exceptions/IncorrectBeatCountException.js b/src/parser/exceptions/IncorrectBeatCountException.js index af883d0f..3dad2277 100644 --- a/src/parser/exceptions/IncorrectBeatCountException.js +++ b/src/parser/exceptions/IncorrectBeatCountException.js @@ -2,25 +2,30 @@ import _isString from 'lodash/isString'; import _isFinite from 'lodash/isFinite'; export default class IncorrectBeatCountException extends Error { - constructor( - { - string, - duration, - currentBeatCount, - beatCount - } = {} - ) { + constructor({ string, duration, currentBeatCount, beatCount } = {}) { if (!string || !_isString(string)) { - throw new TypeError('IncorrectBeatCountException cannot be created without chord string, received: ' + string); + throw new TypeError( + 'IncorrectBeatCountException cannot be created without chord string, received: ' + + string + ); } if (!duration || !_isFinite(duration)) { - throw new TypeError('IncorrectBeatCountException cannot be created without chord duration, received: ' + duration); + throw new TypeError( + 'IncorrectBeatCountException cannot be created without chord duration, received: ' + + duration + ); } if (!currentBeatCount || !_isFinite(currentBeatCount)) { - throw new TypeError('IncorrectBeatCountException cannot be created without currentBeatCount, received: ' + currentBeatCount); + throw new TypeError( + 'IncorrectBeatCountException cannot be created without currentBeatCount, received: ' + + currentBeatCount + ); } if (!beatCount || !_isFinite(beatCount)) { - throw new TypeError('IncorrectBeatCountException cannot be created without beatCount, received: ' + beatCount); + throw new TypeError( + 'IncorrectBeatCountException cannot be created without beatCount, received: ' + + beatCount + ); } super(); @@ -32,4 +37,3 @@ export default class IncorrectBeatCountException extends Error { this.beatCount = beatCount; } } - diff --git a/src/parser/exceptions/InvalidChordRepetitionException.js b/src/parser/exceptions/InvalidChordRepetitionException.js index 282873b3..346b69a0 100644 --- a/src/parser/exceptions/InvalidChordRepetitionException.js +++ b/src/parser/exceptions/InvalidChordRepetitionException.js @@ -1,13 +1,12 @@ import _isString from 'lodash/isString'; export default class InvalidChordRepetitionException extends Error { - constructor( - { - string, - } = {} - ) { + constructor({ string } = {}) { if (!string || !_isString(string)) { - throw new TypeError('InvalidChordRepetitionException cannot be created without chord string, received: ' + string); + throw new TypeError( + 'InvalidChordRepetitionException cannot be created without chord string, received: ' + + string + ); } super(); @@ -16,4 +15,3 @@ export default class InvalidChordRepetitionException extends Error { this.string = string; } } - diff --git a/src/parser/getAllChordsInSong.js b/src/parser/getAllChordsInSong.js index c9f1afa2..3ef0cbf8 100644 --- a/src/parser/getAllChordsInSong.js +++ b/src/parser/getAllChordsInSong.js @@ -12,13 +12,13 @@ export default function getAllChordsInSong(allLines) { const allChords = []; let i; - forEachChordInSong(allLines, chord => { - i = _findIndex(allChords, o => _isEqual(o.model, chord.model)); + forEachChordInSong(allLines, (chord) => { + i = _findIndex(allChords, (o) => _isEqual(o.model, chord.model)); if (i === -1) { allChords.push({ model: _cloneDeep(chord.model), - occurrences: 1 + occurrences: 1, }); } else { allChords[i].occurrences++; diff --git a/src/parser/helper/clearSpaces.js b/src/parser/helper/clearSpaces.js index df850eaa..56238891 100644 --- a/src/parser/helper/clearSpaces.js +++ b/src/parser/helper/clearSpaces.js @@ -1,6 +1,3 @@ export default function clearSpaces(string) { - return string - .replace(/\t+/g, ' ') - .replace(/ +/g, ' ') - .trim(); -} \ No newline at end of file + return string.replace(/\t+/g, ' ').replace(/ +/g, ' ').trim(); +} diff --git a/src/parser/helper/songs.js b/src/parser/helper/songs.js index 9dd9bd8c..0ba0046e 100644 --- a/src/parser/helper/songs.js +++ b/src/parser/helper/songs.js @@ -10,10 +10,10 @@ import lineTypes from '../lineTypes'; export function forEachChordInSong(allLines, fn) { const newLines = _cloneDeep(allLines); - newLines.forEach(line => { + newLines.forEach((line) => { if (line.type === lineTypes.CHORD) { - line.model.allBars.forEach(bar => { - bar.allChords.forEach(chord => { + line.model.allBars.forEach((bar) => { + bar.allChords.forEach((chord) => { fn(chord); }); }); @@ -22,7 +22,6 @@ export function forEachChordInSong(allLines, fn) { return newLines; } - /** * @param {ChordLine} chordLine * @param {Function} fn - to execute on each chord @@ -31,8 +30,8 @@ export function forEachChordInSong(allLines, fn) { export function forEachChordInChordLine(chordLine, fn) { const newChordLine = _cloneDeep(chordLine); - newChordLine.allBars.forEach(bar => { - bar.allChords.forEach(chord => { + newChordLine.allBars.forEach((bar) => { + bar.allChords.forEach((chord) => { fn(chord); }); }); @@ -40,7 +39,6 @@ export function forEachChordInChordLine(chordLine, fn) { return newChordLine; } - /** * @param {SongLine[]} allLines * @param {String} label - the label to select @@ -54,18 +52,17 @@ export function getNthOfLabel(allLines, label, n) { let enableSelect = false; let currentLabel = ''; - allLines.forEach(line => { + allLines.forEach((line) => { if (line.type === lineTypes.SECTION_LABEL) { currentLabel = line.model.label; - if (! typesCount[currentLabel]) { + if (!typesCount[currentLabel]) { typesCount[currentLabel] = 1; } else { typesCount[currentLabel]++; } - enableSelect = (line.id === (label + n)); - + enableSelect = line.id === label + n; } else if (enableSelect) { selected.push(line); } diff --git a/src/parser/matchers/isChord.js b/src/parser/matchers/isChord.js index e02a7d6c..089a34fa 100644 --- a/src/parser/matchers/isChord.js +++ b/src/parser/matchers/isChord.js @@ -1,7 +1,7 @@ -import { chordParserFactory } from 'chord-symbol'; +import { chordParserFactory } from 'chord-symbol'; export default function isChord(potentialChord) { const parseChord = chordParserFactory(); const parsed = parseChord(potentialChord); - return !(parsed.error); + return !parsed.error; } diff --git a/src/parser/matchers/isChordLine.js b/src/parser/matchers/isChordLine.js index b6451def..c952b2ef 100644 --- a/src/parser/matchers/isChordLine.js +++ b/src/parser/matchers/isChordLine.js @@ -10,9 +10,14 @@ export default function isChordLine(line = '') { return clearSpaces(line) .split(' ') .every((potentialChordToken, index) => { - const withoutBeatCount = potentialChordToken.replace(chordBeatCountSymbols, ''); - return isChord(withoutBeatCount) - || (potentialChordToken.match(barRepeatSymbols) && index > 0) - || (withoutBeatCount === syntax.noChord); + const withoutBeatCount = potentialChordToken.replace( + chordBeatCountSymbols, + '' + ); + return ( + isChord(withoutBeatCount) || + (potentialChordToken.match(barRepeatSymbols) && index > 0) || + withoutBeatCount === syntax.noChord + ); }); } diff --git a/src/parser/matchers/isChordLineRepeater.js b/src/parser/matchers/isChordLineRepeater.js index 462effdb..f453b2c9 100644 --- a/src/parser/matchers/isChordLineRepeater.js +++ b/src/parser/matchers/isChordLineRepeater.js @@ -2,5 +2,5 @@ import syntax from '../syntax'; import clearSpaces from '../helper/clearSpaces'; export default function isChordLineRepeater(string) { - return (clearSpaces(string) === syntax.chordLineRepeat); + return clearSpaces(string) === syntax.chordLineRepeat; } diff --git a/src/parser/matchers/isEmptyLine.js b/src/parser/matchers/isEmptyLine.js index 556d4bac..24acad10 100644 --- a/src/parser/matchers/isEmptyLine.js +++ b/src/parser/matchers/isEmptyLine.js @@ -1,5 +1,5 @@ import clearSpaces from '../helper/clearSpaces'; export default function isEmptyLine(string) { - return (clearSpaces(string) === ''); + return clearSpaces(string) === ''; } diff --git a/src/parser/matchers/isSectionLabel.js b/src/parser/matchers/isSectionLabel.js index ba603c29..c517ecde 100644 --- a/src/parser/matchers/isSectionLabel.js +++ b/src/parser/matchers/isSectionLabel.js @@ -1,10 +1,10 @@ import syntax from '../syntax'; -const sectionLabelRegexp = new RegExp('^' + syntax.sectionLabel + '[a-zA-Z]+( x[2-9])?$'); +const sectionLabelRegexp = new RegExp( + '^' + syntax.sectionLabel + '[a-zA-Z]+( x[2-9])?$' +); export default function isSectionLabel(string) { - const found = string - .trim() - .match(sectionLabelRegexp); - return (found !== null); + const found = string.trim().match(sectionLabelRegexp); + return found !== null; } diff --git a/src/parser/matchers/isTimeSignatureString.js b/src/parser/matchers/isTimeSignatureString.js index 19a3afe1..e4a7171b 100644 --- a/src/parser/matchers/isTimeSignatureString.js +++ b/src/parser/matchers/isTimeSignatureString.js @@ -1,7 +1,14 @@ const allowedTimeSignatures = [ - '2/2', '3/2', - '2/4', '3/4', '4/4', '5/4', - '3/8', '6/8', '9/8', '12/8' + '2/2', + '3/2', + '2/4', + '3/4', + '4/4', + '5/4', + '3/8', + '6/8', + '9/8', + '12/8', ]; export default function isTimeSignatureString(string) { diff --git a/src/parser/parseChordLine.js b/src/parser/parseChordLine.js index f4ae527e..fd9b131c 100644 --- a/src/parser/parseChordLine.js +++ b/src/parser/parseChordLine.js @@ -44,9 +44,7 @@ const defaultTimeSignature = parseTimeSignature('4/4'); */ export default function parseChordLine( chordLine, - { - timeSignature = defaultTimeSignature - } = {} + { timeSignature = defaultTimeSignature } = {} ) { const { beatCount } = timeSignature; @@ -64,19 +62,22 @@ export default function parseChordLine( allTokens.forEach((token, tokenIndex) => { if (token.match(barRepeatSymbols)) { if (previousBar) { - for(let i = 0; i < token.length; i++) { + for (let i = 0; i < token.length; i++) { allBars.push(_cloneDeep(previousBar)); } } else { - throw new Error('A chord line cannot start with the barRepeat symbol'); + throw new Error( + 'A chord line cannot start with the barRepeat symbol' + ); } - } else { tokenWithoutBeatCount = token.replace(chordBeatCountSymbols, ''); chord = { string: token, duration: getChordDuration(token, beatCount), - model: isNoChordSymbol(tokenWithoutBeatCount) ? syntax.noChord : parseChord(tokenWithoutBeatCount), + model: isNoChordSymbol(tokenWithoutBeatCount) + ? syntax.noChord + : parseChord(tokenWithoutBeatCount), beat: currentBeatCount + 1, }; currentBeatCount += chord.duration; @@ -95,25 +96,29 @@ export default function parseChordLine( bar = _cloneDeep(emptyBar); currentBeatCount = 0; - } else { - checkInvalidBeatCount(chord, currentBeatCount, beatCount, (allTokens.length === (tokenIndex + 1))); + checkInvalidBeatCount( + chord, + currentBeatCount, + beatCount, + allTokens.length === tokenIndex + 1 + ); } } }); return { chordCount, - allBars + allBars, }; } function isNoChordSymbol(token) { - return (token === syntax.noChord); + return token === syntax.noChord; } function getChordDuration(token, beatCount) { - return ((token.match(chordBeatCountSymbols) || []).length) || beatCount; + return (token.match(chordBeatCountSymbols) || []).length || beatCount; } function checkInvalidChordRepetition(bar, currentChord) { @@ -143,8 +148,10 @@ function checkInvalidBeatCount(chord, currentBeatCount, beatCount, isLast) { } } function hasInvalidBeatCount(currentBeatCount, barBeatCount, isLast) { - return hasTooManyBeats(currentBeatCount, barBeatCount) - || hasTooFewBeats(currentBeatCount, barBeatCount, isLast); + return ( + hasTooManyBeats(currentBeatCount, barBeatCount) || + hasTooFewBeats(currentBeatCount, barBeatCount, isLast) + ); } function hasTooManyBeats(currentBeatCount, barBeatCount) { return currentBeatCount > barBeatCount; diff --git a/src/parser/parseSectionLabel.js b/src/parser/parseSectionLabel.js index 4c0ef85d..c8452570 100644 --- a/src/parser/parseSectionLabel.js +++ b/src/parser/parseSectionLabel.js @@ -14,15 +14,16 @@ import isSectionLabel from './matchers/isSectionLabel'; */ export default function parseSectionLabel(string) { if (!isSectionLabel(string)) { - throw new TypeError('Expected section identifier string, received: ' + string); + throw new TypeError( + 'Expected section identifier string, received: ' + string + ); } - const [ labelSrc, repeatSrc ] = string.trim().split(' '); + const [labelSrc, repeatSrc] = string.trim().split(' '); return { string, label: labelSrc.substring(1), - repeatTimes: (repeatSrc) ? Number.parseInt(repeatSrc.substring(1)) : 0 + repeatTimes: repeatSrc ? Number.parseInt(repeatSrc.substring(1)) : 0, }; } - diff --git a/src/parser/parseSong.js b/src/parser/parseSong.js index cf11d074..88101a79 100644 --- a/src/parser/parseSong.js +++ b/src/parser/parseSong.js @@ -26,23 +26,20 @@ import getAllChordsInSong from './getAllChordsInSong'; * @returns {Song} */ export default function parseSong(songSrc) { - const songArray = (!_isArray(songSrc)) ? songSrc.split('\n') : songSrc; + const songArray = !_isArray(songSrc) ? songSrc.split('\n') : songSrc; const songLines = songLinesFactory(); /** * @type {SongLine[]} */ - songArray - .map(escapeHTML) - .map(stripTags) - .forEach(songLines.addLine); + songArray.map(escapeHTML).map(stripTags).forEach(songLines.addLine); const allLines = songLines.asArray(); const allChords = getAllChordsInSong(allLines); return { allLines, - allChords + allChords, }; } diff --git a/src/parser/parseTextLine.js b/src/parser/parseTextLine.js new file mode 100644 index 00000000..74fe9626 --- /dev/null +++ b/src/parser/parseTextLine.js @@ -0,0 +1,6 @@ +/** + * @typedef {Object} TextLine + * @type {Object} + * @property {Number} chordCount - number of chords in the line + * @property {Bar[]} allBars + */ diff --git a/src/parser/parseTimeSignature.js b/src/parser/parseTimeSignature.js index 33ec5cfd..376289d8 100644 --- a/src/parser/parseTimeSignature.js +++ b/src/parser/parseTimeSignature.js @@ -15,7 +15,9 @@ import isTimeSignatureString from './matchers/isTimeSignatureString'; */ export default function parseTimeSignature(string) { if (!isTimeSignatureString(string)) { - throw new TypeError('Expected time signature string, received: ' + string); + throw new TypeError( + 'Expected time signature string, received: ' + string + ); } const array = string.split('/'); @@ -27,7 +29,6 @@ export default function parseTimeSignature(string) { if (value === 2) { beatCount = count * 2; - } else if (value === 8) { beatCount = count / 3; } @@ -39,4 +40,3 @@ export default function parseTimeSignature(string) { beatCount, }; } - diff --git a/src/parser/songLinesFactory.js b/src/parser/songLinesFactory.js index 28251f4d..9e1d9735 100644 --- a/src/parser/songLinesFactory.js +++ b/src/parser/songLinesFactory.js @@ -101,7 +101,7 @@ export default function songLinesFactory() { id: currentSectionLabel.label + currentSectionStats.count, }; - shouldRepeatSection = (currentSectionLabel.repeatTimes > 0); + shouldRepeatSection = currentSectionLabel.repeatTimes > 0; previousSectionLabelLine = _cloneDeep(line); if (!isFirstOfLabel(currentSectionLabel, allLines)) { @@ -130,14 +130,15 @@ export default function songLinesFactory() { function getChordLine(string) { let line; try { - const model = parseChordLine(string, { timeSignature: currentTimeSignature }); + const model = parseChordLine(string, { + timeSignature: currentTimeSignature, + }); line = { string, type: lineTypes.CHORD, model, }; previousChordLine = line; - } catch (e) { line = getTextLine(string); } @@ -151,7 +152,7 @@ export default function songLinesFactory() { if (previousChordLine) { return { ..._cloneDeep(previousChordLine), - isFromChordLineRepeater: true + isFromChordLineRepeater: true, }; } return getTextLine(string); @@ -166,7 +167,7 @@ export default function songLinesFactory() { type: lineTypes.TEXT, }; } - + function increaseSectionStats(label, isRepeated = false) { if (!sectionsStats[label]) { sectionsStats[label] = { @@ -207,17 +208,25 @@ export default function songLinesFactory() { } function repeatSection(lineIndex, allSrcLines) { - if (shouldRepeatSection && isLastLineOfSection(lineIndex, allSrcLines)) { - const toRepeat = getNthOfLabel(allLines, currentSectionLabel.label, currentSectionStats.count) - .map(line => ({ - ..._cloneDeep(line), - isFromSectionRepeat: true - })); + if ( + shouldRepeatSection && + isLastLineOfSection(lineIndex, allSrcLines) + ) { + const toRepeat = getNthOfLabel( + allLines, + currentSectionLabel.label, + currentSectionStats.count + ).map((line) => ({ + ..._cloneDeep(line), + isFromSectionRepeat: true, + })); let sectionLabelLine; for (let i = 1; i < currentSectionLabel.repeatTimes; i++) { increaseSectionStats(currentSectionLabel.label, true); - currentSectionStats = getSectionCount(currentSectionLabel.label); + currentSectionStats = getSectionCount( + currentSectionLabel.label + ); sectionLabelLine = { ..._cloneDeep(previousSectionLabelLine), @@ -237,19 +246,14 @@ export default function songLinesFactory() { let line; if (isTimeSignature(lineSrc)) { line = getTimeSignatureLine(lineSrc); - } else if (isSectionLabel(lineSrc)) { line = getSectionLabelLine(lineSrc); - } else if (isChordLine(lineSrc)) { line = getChordLine(lineSrc); - } else if (isChordLineRepeater(lineSrc)) { line = getPreviousChordLine(lineSrc); - } else if (isEmptyLine(lineSrc)) { line = getEmptyLine(lineSrc); - } else { line = getTextLine(lineSrc); } @@ -259,7 +263,6 @@ export default function songLinesFactory() { allLines.push(line); repeatSection(lineIndex, allSrcLines); - }, /** @@ -267,22 +270,26 @@ export default function songLinesFactory() { */ asArray() { return _cloneDeep(allLines); - } + }, }; } function isFirstOfLabel(currentLabel, allLines) { - return allLines.every(line => - (line.type === lineTypes.SECTION_LABEL && line.model.label !== currentLabel.label) + return allLines.every( + (line) => + line.type === lineTypes.SECTION_LABEL && + line.model.label !== currentLabel.label ); } function shouldRepeatLineFromBlueprint(blueprintLine, currentLine) { - return blueprintLine - && blueprintLine.type !== lineTypes.TEXT - && blueprintLine.type !== lineTypes.EMPTY_LINE - && blueprintLine.type !== currentLine.type - && currentLine.type !== lineTypes.EMPTY_LINE; + return ( + blueprintLine && + blueprintLine.type !== lineTypes.TEXT && + blueprintLine.type !== lineTypes.EMPTY_LINE && + blueprintLine.type !== currentLine.type && + currentLine.type !== lineTypes.EMPTY_LINE + ); } function isLastLineOfSection(lineIndex, allSrcLines) { diff --git a/src/renderer/components/renderBarContent.js b/src/renderer/components/renderBarContent.js index 3ba3d1d4..cab95705 100644 --- a/src/renderer/components/renderBarContent.js +++ b/src/renderer/components/renderBarContent.js @@ -17,8 +17,12 @@ export default function renderBarContent(bar) { let spacesAfter = 0; const barContent = bar.allChords.reduce((rendering, chord) => { - spacesWithin = _isFinite(chord.spacesWithin) ? chord.spacesWithin : defaultSpacesWithin; - spacesAfter = _isFinite(chord.spacesAfter) ? chord.spacesAfter : defaultSpacesAfter; + spacesWithin = _isFinite(chord.spacesWithin) + ? chord.spacesWithin + : defaultSpacesWithin; + spacesAfter = _isFinite(chord.spacesAfter) + ? chord.spacesAfter + : defaultSpacesAfter; rendering += renderChord(chord.symbol) + diff --git a/src/renderer/components/renderChordLine.js b/src/renderer/components/renderChordLine.js index 7dbd2b31..c4ca090a 100644 --- a/src/renderer/components/renderChordLine.js +++ b/src/renderer/components/renderChordLine.js @@ -8,14 +8,14 @@ import barSeparatorTpl from './tpl/barSeparator.hbs'; * @returns {String} rendered html */ export default function renderChordLine(chordLineModel) { - const allBarsRendered = chordLineModel.allBars - .map(bar => renderBarContent(bar)); + const allBarsRendered = chordLineModel.allBars.map((bar) => + renderBarContent(bar) + ); const barSeparator = barSeparatorTpl(); - const chordLine = barSeparator + - allBarsRendered.join(barSeparator) + - barSeparator; + const chordLine = + barSeparator + allBarsRendered.join(barSeparator) + barSeparator; return chordLineTpl({ chordLine }); } diff --git a/src/renderer/components/renderChordSymbol.js b/src/renderer/components/renderChordSymbol.js index 197ad28b..de261641 100644 --- a/src/renderer/components/renderChordSymbol.js +++ b/src/renderer/components/renderChordSymbol.js @@ -4,6 +4,6 @@ import chordSymbolTpl from './tpl/chordSymbol.hbs'; * @param {String} chordSymbol * @returns {String} rendered html */ -export default function renderChordSymbol (chordSymbol) { +export default function renderChordSymbol(chordSymbol) { return chordSymbolTpl({ chordSymbol }); } diff --git a/src/renderer/components/renderLine.js b/src/renderer/components/renderLine.js index e0dc4b00..b125ea55 100644 --- a/src/renderer/components/renderLine.js +++ b/src/renderer/components/renderLine.js @@ -7,11 +7,14 @@ import lineTpl from './tpl/line.hbs'; * @param {Boolean} isFromChordLineRepeater * @returns {String} rendered html */ -export default function render(line, { - isFromSectionRepeat = false, - isFromAutoRepeatChords = false, - isFromChordLineRepeater = false, -} = {}) { +export default function render( + line, + { + isFromSectionRepeat = false, + isFromAutoRepeatChords = false, + isFromChordLineRepeater = false, + } = {} +) { const lineClasses = ['cmLine']; if (isFromSectionRepeat) { lineClasses.push('cmLine--isFromSectionRepeat'); diff --git a/src/renderer/components/renderSectionLabel.js b/src/renderer/components/renderSectionLabel.js index 8a8fab7c..669f0fa7 100644 --- a/src/renderer/components/renderSectionLabel.js +++ b/src/renderer/components/renderSectionLabel.js @@ -12,28 +12,26 @@ const labelsMapping = { v: 'verse', }; - /** * @param {SongSectionLabelLine} sectionLabelLine * @param {Boolean} expandSectionRepeats * @param {Object} sectionsStats - key = section label, value = section count in song * @returns {String} rendered html */ -export default function renderSectionLabel(sectionLabelLine, { - expandSectionRepeats = true, - sectionsStats = {}, -} = {}) { +export default function renderSectionLabel( + sectionLabelLine, + { expandSectionRepeats = true, sectionsStats = {} } = {} +) { const { model, index, indexWithoutRepeats } = sectionLabelLine; const labelRaw = labelsMapping[model.label] ? labelsMapping[model.label] : model.label; - let rendered = labelRaw[0].toUpperCase() + labelRaw.substring(1); if (sectionsStats[model.label] > 1) { rendered += ' '; - rendered += (expandSectionRepeats) ? index : indexWithoutRepeats; + rendered += expandSectionRepeats ? index : indexWithoutRepeats; } if (!expandSectionRepeats && model.repeatTimes) { diff --git a/src/renderer/components/renderSong.js b/src/renderer/components/renderSong.js index 6a27e715..e4089616 100644 --- a/src/renderer/components/renderSong.js +++ b/src/renderer/components/renderSong.js @@ -33,29 +33,32 @@ import lineTypes from '../../parser/lineTypes'; * @param {Boolean} useShortNamings * @returns {String} rendered HTML */ -export default function renderSong(parsedSong, { - alignBars = false, - transposeValue = 0, - accidentalsType = 'auto', - harmonizeAccidentals = true, - expandSectionRepeats = true, - autoRepeatChords = true, - simplifyChords = 'none', - useShortNamings = true, -} = {}) { - +export default function renderSong( + parsedSong, + { + alignBars = false, + transposeValue = 0, + accidentalsType = 'auto', + harmonizeAccidentals = true, + expandSectionRepeats = true, + autoRepeatChords = true, + simplifyChords = 'none', + useShortNamings = true, + } = {} +) { let { allLines, allChords } = parsedSong; - const accidental = (accidentalsType === 'auto') - ? getMainAccidental(allChords) - : accidentalsType; + const accidental = + accidentalsType === 'auto' + ? getMainAccidental(allChords) + : accidentalsType; const renderChord = chordRendererFactory({ simplify: simplifyChords, useShortNamings, transposeValue, harmonizeAccidentals, - useFlats: (accidental === 'flat'), + useFlats: accidental === 'flat', }); allLines = forEachChordInSong(allLines, (chord) => { @@ -69,26 +72,25 @@ export default function renderSong(parsedSong, { const song = allLines .filter(shouldRenderLine) - .map(line => { + .map((line) => { let rendered; if (line.type === lineTypes.CHORD) { // todo: move this in renderChordLine - const spaced = (alignBars) + const spaced = alignBars ? alignedChordSpacer(line.model, maxBeatsWidth) : simpleChordSpacer(line.model); rendered = renderChordLine(spaced); - } else if (line.type === lineTypes.EMPTY_LINE) { rendered = renderEmptyLine(); - } else if (line.type === lineTypes.SECTION_LABEL) { - rendered = renderSectionLabel(line, { sectionsStats, expandSectionRepeats }); - + rendered = renderSectionLabel(line, { + sectionsStats, + expandSectionRepeats, + }); } else if (line.type === lineTypes.TIME_SIGNATURE) { rendered = renderTimeSignature(line); - } else { rendered = renderTextLine(line); } @@ -101,17 +103,16 @@ export default function renderSong(parsedSong, { .filter(Boolean) .join('\n'); - function shouldRenderLine(line) { if (line.type === lineTypes.SECTION_LABEL) { - shouldSkipRepeatedSectionLine = (line.isFromSectionRepeat === true && !expandSectionRepeats); + shouldSkipRepeatedSectionLine = + line.isFromSectionRepeat === true && !expandSectionRepeats; } - const shouldSkipAutoRepeatChordLine = (line.isFromAutoRepeatChords && !autoRepeatChords); + const shouldSkipAutoRepeatChordLine = + line.isFromAutoRepeatChords && !autoRepeatChords; return !shouldSkipRepeatedSectionLine && !shouldSkipAutoRepeatChordLine; } return songTpl({ song }); } - - diff --git a/src/renderer/helpers/getChordSymbol.js b/src/renderer/helpers/getChordSymbol.js index de89d5c4..4ec4ae18 100644 --- a/src/renderer/helpers/getChordSymbol.js +++ b/src/renderer/helpers/getChordSymbol.js @@ -9,6 +9,6 @@ const defaultRenderChord = chordRendererFactory(); * @param {Function} renderChord * @returns {string} */ -export default function(model, renderChord = defaultRenderChord) { - return (model === syntax.noChord) ? noChordSymbol : renderChord(model); +export default function (model, renderChord = defaultRenderChord) { + return model === syntax.noChord ? noChordSymbol : renderChord(model); } diff --git a/src/renderer/helpers/getMainAccidental.js b/src/renderer/helpers/getMainAccidental.js index fb3add3e..03e87598 100644 --- a/src/renderer/helpers/getMainAccidental.js +++ b/src/renderer/helpers/getMainAccidental.js @@ -12,8 +12,8 @@ export default function getMainAccidental(allChords) { let sharpCount = 0; allChords - .filter(chord => (chord.model !== syntax.noChord)) - .forEach(chord => { + .filter((chord) => chord.model !== syntax.noChord) + .forEach((chord) => { rootNote = chord.model.formatted.rootNote; if (rootNote.length === 2) { @@ -27,5 +27,5 @@ export default function getMainAccidental(allChords) { } }); - return (flatCount > sharpCount) ? 'flat' : 'sharp'; + return flatCount > sharpCount ? 'flat' : 'sharp'; } diff --git a/src/renderer/helpers/getSectionsStats.js b/src/renderer/helpers/getSectionsStats.js index 05e9ab89..8c877d50 100644 --- a/src/renderer/helpers/getSectionsStats.js +++ b/src/renderer/helpers/getSectionsStats.js @@ -10,11 +10,10 @@ export default function getSectionsStats(allLines) { const stats = {}; allLines - .filter(line => line.type === lineTypes.SECTION_LABEL) - .forEach(line => { + .filter((line) => line.type === lineTypes.SECTION_LABEL) + .forEach((line) => { if (!stats[line.model.label]) { stats[line.model.label] = 1; - } else { stats[line.model.label]++; } diff --git a/src/renderer/spacers/chord/aligned.js b/src/renderer/spacers/chord/aligned.js index eb09d4b9..5b68016e 100644 --- a/src/renderer/spacers/chord/aligned.js +++ b/src/renderer/spacers/chord/aligned.js @@ -9,17 +9,22 @@ export default function space(chordLineInput, maxBeatsWidth) { let beatMaxWidth; chordLine.allBars.forEach((bar, barIndex) => { - bar.allChords.forEach(chord => { - chord.spacesWithin = maxBeatsWidth[barIndex][chord.beat] - chord.symbol.length; + bar.allChords.forEach((chord) => { + chord.spacesWithin = + maxBeatsWidth[barIndex][chord.beat] - chord.symbol.length; chord.spacesAfter = 0; if (chord.beat !== bar.timeSignature.beatCount) { chord.spacesAfter = spacesAfterDefault; - for (let i = (chord.beat + 1); i < (chord.beat + chord.duration); i++) { + for ( + let i = chord.beat + 1; + i < chord.beat + chord.duration; + i++ + ) { beatMaxWidth = maxBeatsWidth[barIndex][i]; - chord.spacesAfter += (beatMaxWidth) + chord.spacesAfter += beatMaxWidth ? beatMaxWidth : emptyBeatSpaces; diff --git a/src/renderer/spacers/chord/getMaxBeatsWidth.js b/src/renderer/spacers/chord/getMaxBeatsWidth.js index 60b52ed7..7160e22c 100644 --- a/src/renderer/spacers/chord/getMaxBeatsWidth.js +++ b/src/renderer/spacers/chord/getMaxBeatsWidth.js @@ -8,8 +8,8 @@ export default function getMaxBeatsWidth(allLines) { const maxBeatsWidth = []; allLines - .filter(line => line.type === lineTypes.CHORD) - .forEach(line => { + .filter((line) => line.type === lineTypes.CHORD) + .forEach((line) => { line.model.allBars.forEach((bar, barIndex) => { if (!maxBeatsWidth[barIndex]) { maxBeatsWidth[barIndex] = {}; @@ -18,7 +18,7 @@ export default function getMaxBeatsWidth(allLines) { maxBeatsWidth[barIndex][i] = 0; } } - bar.allChords.forEach(chord => { + bar.allChords.forEach((chord) => { maxBeatsWidth[barIndex][chord.beat] = Math.max( maxBeatsWidth[barIndex][chord.beat], chord.symbol.length diff --git a/src/renderer/spacers/chord/simple.js b/src/renderer/spacers/chord/simple.js index 246704c2..f079a5db 100644 --- a/src/renderer/spacers/chord/simple.js +++ b/src/renderer/spacers/chord/simple.js @@ -4,30 +4,30 @@ const defaultSpacesAfter = 2; const allMasks = { 0: { - '': [] + '': [], }, 2: { - '2': [3], // 'A ' - '11': [3, 2], // 'A B ', + 2: [3], // 'A ' + 11: [3, 2], // 'A B ', }, 3: { - '3': [3], // 'A ' - '12': [2, 4], // 'A B ' - '21': [6, 0], // 'A C' - '111': [2, 2, 0], // 'A B C' + 3: [3], // 'A ' + 12: [2, 4], // 'A B ' + 21: [6, 0], // 'A C' + 111: [2, 2, 0], // 'A B C' }, 4: { - '4': [3], // 'A ', - '13': [1, 4], // 'A B ', - '22': [3, 2], // 'A B ', - '31': [5, 0], // 'A B', - '112': [1, 1, 3], // 'A B C ', - '121': [1, 4, 0], // 'A B C', - '211': [4, 1, 0], // 'A B C', - '1111': [2, 2, 2, 0], // 'A B C D', + 4: [3], // 'A ', + 13: [1, 4], // 'A B ', + 22: [3, 2], // 'A B ', + 31: [5, 0], // 'A B', + 112: [1, 1, 3], // 'A B C ', + 121: [1, 4, 0], // 'A B C', + 211: [4, 1, 0], // 'A B C', + 1111: [2, 2, 2, 0], // 'A B C D', }, }; @@ -42,13 +42,13 @@ export default function space(chordLineInput) { let chordPattern = ''; let chordSpaces = []; - chordLine.allBars.forEach(bar => { + chordLine.allBars.forEach((bar) => { beatCount = 0; chordPattern = ''; chordSpaces = []; chordCount = bar.allChords.length; - bar.allChords.forEach(chord => { + bar.allChords.forEach((chord) => { chordPattern += chord.duration.toString(); beatCount += chord.duration; }); @@ -58,9 +58,10 @@ export default function space(chordLineInput) { } for (let i = 0; i < chordCount; i++) { - bar.allChords[i].spacesAfter = (typeof(chordSpaces[i]) !== 'undefined') - ? chordSpaces[i] - : defaultSpacesAfter; + bar.allChords[i].spacesAfter = + typeof chordSpaces[i] !== 'undefined' + ? chordSpaces[i] + : defaultSpacesAfter; } }); diff --git a/tests/.eslintrc.js b/tests/.eslintrc.js index efdbd388..d3916f29 100644 --- a/tests/.eslintrc.js +++ b/tests/.eslintrc.js @@ -1,16 +1,16 @@ module.exports = { - 'env': { - 'jest': true, - 'node': true, + env: { + jest: true, + node: true, }, - 'rules': { - 'max-len': [ 'off' ], - 'max-lines': [ 'off' ], - 'max-lines-per-function': [ 'off' ], - 'max-params': [ 'off' ], + rules: { + 'max-len': ['off'], + 'max-lines': ['off'], + 'max-lines-per-function': ['off'], + 'max-params': ['off'], - 'no-restricted-imports': [ 'off' ], + 'no-restricted-imports': ['off'], - 'no-unsanitized/property':[ 'off' ], - } + 'no-unsanitized/property': ['off'], + }, }; diff --git a/tests/integration/parser/parseSong.spec.js b/tests/integration/parser/parseSong.spec.js index e0698c33..1dd92c3f 100644 --- a/tests/integration/parser/parseSong.spec.js +++ b/tests/integration/parser/parseSong.spec.js @@ -3,7 +3,6 @@ import parseChord from '../../../src/parser/parseChord'; import parseSong from '../../../src/parser/parseSong'; import parseTimeSignature from '../../../src/parser/parseTimeSignature'; - describe('parseSong', () => { test('', () => { expect(true).toBe(true); @@ -30,23 +29,62 @@ F. Em. Dm. C. Let it be`; const expected = { allLines: [ - { type: 'timeSignature', string: '4/4', model: parseTimeSignature('4/4') }, - { type: 'chord', string: 'C.. G..', model: parseChordLine('C.. G..')} , - { type: 'text', string: 'When I find myself in times of trouble' }, - { type: 'chord', string: 'Am.. F..', model: parseChordLine('Am.. F..') }, + { + type: 'timeSignature', + string: '4/4', + model: parseTimeSignature('4/4'), + }, + { + type: 'chord', + string: 'C.. G..', + model: parseChordLine('C.. G..'), + }, + { + type: 'text', + string: 'When I find myself in times of trouble', + }, + { + type: 'chord', + string: 'Am.. F..', + model: parseChordLine('Am.. F..'), + }, { type: 'text', string: 'Mother mary comes to me' }, - { type: 'chord', string: 'C.. G..', model: parseChordLine('C.. G..') }, + { + type: 'chord', + string: 'C.. G..', + model: parseChordLine('C.. G..'), + }, { type: 'text', string: 'Speaking words of wisdom' }, - { type: 'chord', string: 'F. Em. Dm. C.', model: parseChordLine('F. Em. Dm. C.') }, + { + type: 'chord', + string: 'F. Em. Dm. C.', + model: parseChordLine('F. Em. Dm. C.'), + }, { type: 'text', string: 'Let it be' }, { type: 'emptyLine', string: '' }, - { type: 'chord', string: 'Am.. G..', model: parseChordLine('Am.. G..') }, + { + type: 'chord', + string: 'Am.. G..', + model: parseChordLine('Am.. G..'), + }, { type: 'text', string: 'Let it be, let it be' }, - { type: 'chord', string: 'C.. F..', model: parseChordLine('C.. F..') }, + { + type: 'chord', + string: 'C.. F..', + model: parseChordLine('C.. F..'), + }, { type: 'text', string: 'Let it be, let it be' }, - { type: 'chord', string: 'C.. G..', model: parseChordLine('C.. G..') }, + { + type: 'chord', + string: 'C.. G..', + model: parseChordLine('C.. G..'), + }, { type: 'text', string: 'Whispers words of wisdom' }, - { type: 'chord', string: 'F. Em. Dm. C.', model: parseChordLine('F. Em. Dm. C.') }, + { + type: 'chord', + string: 'F. Em. Dm. C.', + model: parseChordLine('F. Em. Dm. C.'), + }, { type: 'text', string: 'Let it be' }, ], allChords: [ @@ -56,7 +94,7 @@ Let it be`; { model: parseChord('F'), occurrences: 4 }, { model: parseChord('Em'), occurrences: 2 }, { model: parseChord('Dm'), occurrences: 2 }, - ] + ], }; expect(parseSong(input)).toEqual(expected); @@ -67,24 +105,34 @@ Let it be`; 'C.. G..', 'When I find myself in times of trouble', 'Am.. F..', - 'Mother mary comes to me' + 'Mother mary comes to me', ]; const expected = { allLines: [ - { type: 'chord', string: 'C.. G..', model: parseChordLine('C.. G..') }, - { type: 'text', string: 'When I find myself in times of trouble' }, - { type: 'chord', string: 'Am.. F..', model: parseChordLine('Am.. F..') }, - { type: 'text', string: 'Mother mary comes to me' }, + { + type: 'chord', + string: 'C.. G..', + model: parseChordLine('C.. G..'), + }, + { + type: 'text', + string: 'When I find myself in times of trouble', + }, + { + type: 'chord', + string: 'Am.. F..', + model: parseChordLine('Am.. F..'), + }, + { type: 'text', string: 'Mother mary comes to me' }, ], allChords: [ { model: parseChord('C'), occurrences: 1 }, { model: parseChord('G'), occurrences: 1 }, { model: parseChord('Am'), occurrences: 1 }, { model: parseChord('F'), occurrences: 1 }, - ] + ], }; expect(parseSong(input)).toEqual(expected); }); - }); diff --git a/tests/integration/renderer/components/renderSong/renderSong.spec.js b/tests/integration/renderer/components/renderSong/renderSong.spec.js index f33c1e90..5490eccd 100644 --- a/tests/integration/renderer/components/renderSong/renderSong.spec.js +++ b/tests/integration/renderer/components/renderSong/renderSong.spec.js @@ -21,17 +21,34 @@ function removeLastLine(fileContent) { } describe.each([ - - ['base rendering', 'song1-input.txt', 'song1-output-simple.txt'], - ['base rendering', 'song1-input.txt', 'song1-output-simple.txt', {} ], - ['no transposing', 'song1-input.txt', 'song1-output-simple.txt', { alignBars: false, harmonizeAccidentals: false } ], - ['aligned rendering', 'song1-input.txt', 'song1-output-aligned.txt', { alignBars: true } ], - ['transposed', 'song1-input.txt', 'song1-output-transposed.txt', { transposeValue: -4, accidentalsType: 'flat' } ], - + ['base rendering', 'song1-input.txt', 'song1-output-simple.txt'], + ['base rendering', 'song1-input.txt', 'song1-output-simple.txt', {}], + [ + 'no transposing', + 'song1-input.txt', + 'song1-output-simple.txt', + { alignBars: false, harmonizeAccidentals: false }, + ], + [ + 'aligned rendering', + 'song1-input.txt', + 'song1-output-aligned.txt', + { alignBars: true }, + ], + [ + 'transposed', + 'song1-input.txt', + 'song1-output-transposed.txt', + { transposeValue: -4, accidentalsType: 'flat' }, + ], ])('Render components: %s', (title, inputFile, outputFile, options) => { test('produces expected rendering', () => { - const input = removeLastLine(fs.readFileSync(path.resolve(dataFolder, inputFile), 'utf8')); - const output = removeLastLine(fs.readFileSync(path.resolve(dataFolder, outputFile), 'utf8')); + const input = removeLastLine( + fs.readFileSync(path.resolve(dataFolder, inputFile), 'utf8') + ); + const output = removeLastLine( + fs.readFileSync(path.resolve(dataFolder, outputFile), 'utf8') + ); const parsedSong = parseSong(input); const rendered = renderSong(parsedSong, options); diff --git a/tests/unit/core/addEventEmitter.spec.js b/tests/unit/core/addEventEmitter.spec.js index 710bfcb4..958997ad 100644 --- a/tests/unit/core/addEventEmitter.spec.js +++ b/tests/unit/core/addEventEmitter.spec.js @@ -6,12 +6,7 @@ describe('addEventEmitter', () => { }); }); -describe.each([ - 'on', - 'once', - 'off', - 'emit' -])('API: .%s()', (method) => { +describe.each(['on', 'once', 'off', 'emit'])('API: .%s()', (method) => { test('Method exists', () => { const withEmitter = addEventEmitter({}); expect(withEmitter[method]).toBeInstanceOf(Function); @@ -45,18 +40,18 @@ describe('.on()', () => { const withEmitter = addEventEmitter({}); const listener = jest.fn(); - const params = [ - { test: 'test' }, - ['4', '5'], - 'string', - 69 - ]; + const params = [{ test: 'test' }, ['4', '5'], 'string', 69]; withEmitter.on('test', listener); withEmitter.emit('test', params[0], params[1], params[2], params[3]); expect(listener).toHaveBeenCalledTimes(1); - expect(listener).toHaveBeenLastCalledWith(params[0], params[1], params[2], params[3]); + expect(listener).toHaveBeenLastCalledWith( + params[0], + params[1], + params[2], + params[3] + ); }); }); diff --git a/tests/unit/core/dom/htmlToElement.spec.js b/tests/unit/core/dom/htmlToElement.spec.js index a6779f25..5efebcbe 100644 --- a/tests/unit/core/dom/htmlToElement.spec.js +++ b/tests/unit/core/dom/htmlToElement.spec.js @@ -8,7 +8,8 @@ describe('htmlToElement', () => { describe('Behavior', () => { test('Should return valid html from string', () => { - const html = '

Content

'; + const html = + '

Content

'; const element = htmlToElement(html); @@ -19,7 +20,9 @@ describe('Behavior', () => { expect(element.firstChild.nodeType).toEqual(1); expect(element.firstChild.nodeName).toEqual('P'); - expect(element.firstChild.classList.contains('test-content')).toEqual(true); + expect(element.firstChild.classList.contains('test-content')).toEqual( + true + ); expect(element.firstChild.textContent).toEqual('Content'); }); diff --git a/tests/unit/core/dom/stripTags.spec.js b/tests/unit/core/dom/stripTags.spec.js index e5fe6767..a72eb3d0 100644 --- a/tests/unit/core/dom/stripTags.spec.js +++ b/tests/unit/core/dom/stripTags.spec.js @@ -7,13 +7,14 @@ describe('stripTags', () => { }); describe.each([ - [undefined, ''], ['', ''], ['plain text', 'plain text'], ['

Content

', 'Content'], - ['

This text is bold and also italic

', 'This text is bold and also italic'], - + [ + '

This text is bold and also italic

', + 'This text is bold and also italic', + ], ])('Should strip tags out of %s', (input, output) => { test('Correctly strips html tags', () => { expect(stripTags(input)).toEqual(output); diff --git a/tests/unit/parser/exceptions/IncorrectBeatCountExpection.spec.js b/tests/unit/parser/exceptions/IncorrectBeatCountExpection.spec.js index a3fdd6e3..98621340 100644 --- a/tests/unit/parser/exceptions/IncorrectBeatCountExpection.spec.js +++ b/tests/unit/parser/exceptions/IncorrectBeatCountExpection.spec.js @@ -12,7 +12,7 @@ describe('Behavior', () => { string: 'Cm7...', duration: 3, currentBeatCount: 6, - beatCount: 4 + beatCount: 4, }); expect(error).toBeInstanceOf(IncorrectBeatCountException); expect(error.string).toEqual('Cm7...'); @@ -22,28 +22,50 @@ describe('Behavior', () => { }); test('Throw if given no parameter', () => { - const throwingFn = () => { throw new IncorrectBeatCountException(); }; + const throwingFn = () => { + throw new IncorrectBeatCountException(); + }; expect(throwingFn).toThrow(TypeError); - expect(throwingFn).toThrow('IncorrectBeatCountException cannot be created without chord string, received: undefined'); + expect(throwingFn).toThrow( + 'IncorrectBeatCountException cannot be created without chord string, received: undefined' + ); }); }); describe.each([ - ['no string', 'string', 'IncorrectBeatCountException cannot be created without chord string, received: undefined'], - ['no duration', 'duration', 'IncorrectBeatCountException cannot be created without chord duration, received: undefined'], - ['no currentBeatCount', 'currentBeatCount', 'IncorrectBeatCountException cannot be created without currentBeatCount, received: undefined'], - ['no beatCount', 'beatCount', 'IncorrectBeatCountException cannot be created without beatCount, received: undefined'], + [ + 'no string', + 'string', + 'IncorrectBeatCountException cannot be created without chord string, received: undefined', + ], + [ + 'no duration', + 'duration', + 'IncorrectBeatCountException cannot be created without chord duration, received: undefined', + ], + [ + 'no currentBeatCount', + 'currentBeatCount', + 'IncorrectBeatCountException cannot be created without currentBeatCount, received: undefined', + ], + [ + 'no beatCount', + 'beatCount', + 'IncorrectBeatCountException cannot be created without beatCount, received: undefined', + ], ])('Throw TypeError on %s', (title, propertyToRemove, message) => { test('Test details', () => { const errorParameters = { string: 'Cm7...', duration: 3, currentBeatCount: 6, - BeatCount: 4 + BeatCount: 4, }; delete errorParameters[propertyToRemove]; - const throwingFn = () => { throw new IncorrectBeatCountException(errorParameters); }; + const throwingFn = () => { + throw new IncorrectBeatCountException(errorParameters); + }; expect(throwingFn).toThrow(TypeError); expect(throwingFn).toThrow(message); }); diff --git a/tests/unit/parser/exceptions/InvalidChordRepetitionException.spec.js b/tests/unit/parser/exceptions/InvalidChordRepetitionException.spec.js index b2521df4..e62a1681 100644 --- a/tests/unit/parser/exceptions/InvalidChordRepetitionException.spec.js +++ b/tests/unit/parser/exceptions/InvalidChordRepetitionException.spec.js @@ -16,16 +16,22 @@ describe('Behavior', () => { }); test('Throw if given no parameter', () => { - const throwingFn = () => { throw new InvalidChordRepetitionException(); }; + const throwingFn = () => { + throw new InvalidChordRepetitionException(); + }; expect(throwingFn).toThrow(TypeError); - expect(throwingFn).toThrow('InvalidChordRepetitionException cannot be created without chord string, received: undefined'); + expect(throwingFn).toThrow( + 'InvalidChordRepetitionException cannot be created without chord string, received: undefined' + ); }); }); describe.each([ - - ['no string', 'string', 'InvalidChordRepetitionException cannot be created without chord string, received: undefined'], - + [ + 'no string', + 'string', + 'InvalidChordRepetitionException cannot be created without chord string, received: undefined', + ], ])('Throw TypeError on %s', (title, propertyToRemove, message) => { test('Test details', () => { const errorParameters = { @@ -33,7 +39,9 @@ describe.each([ }; delete errorParameters[propertyToRemove]; - const throwingFn = () => { throw new InvalidChordRepetitionException(errorParameters); }; + const throwingFn = () => { + throw new InvalidChordRepetitionException(errorParameters); + }; expect(throwingFn).toThrow(TypeError); expect(throwingFn).toThrow(message); }); diff --git a/tests/unit/parser/helpers/clearSpaces.spec.js b/tests/unit/parser/helpers/clearSpaces.spec.js index 914d4036..54dfc49a 100644 --- a/tests/unit/parser/helpers/clearSpaces.spec.js +++ b/tests/unit/parser/helpers/clearSpaces.spec.js @@ -7,19 +7,18 @@ describe('clearSpaces', () => { }); describe.each([ - ['Empty input', '', ''], - ['Preserve text', 'test', 'test'], - ['1 space', ' ', ''], - ['2 spaces', ' ', ''], - ['multiple spaces', ' ', ''], - ['1 tab', ' ', ''], - ['2 tabs', ' ', ''], - ['multiple tabs', ' ', ''], - - ['spaces between tokens', ' A B C ', 'A B C'], - ['tabs between tokens', ' A B C ', 'A B C'], - ['mix of spaces & tabs', ' A B C D E', 'A B C D E'], + ['Empty input', '', ''], + ['Preserve text', 'test', 'test'], + ['1 space', ' ', ''], + ['2 spaces', ' ', ''], + ['multiple spaces', ' ', ''], + ['1 tab', ' ', ''], + ['2 tabs', ' ', ''], + ['multiple tabs', ' ', ''], + ['spaces between tokens', ' A B C ', 'A B C'], + ['tabs between tokens', ' A B C ', 'A B C'], + ['mix of spaces & tabs', ' A B C D E', 'A B C D E'], ])('Group title for %s', (title, input, output) => { test('Test details', () => { expect(clearSpaces(input)).toEqual(output); diff --git a/tests/unit/parser/helpers/song.spec.js b/tests/unit/parser/helpers/song.spec.js index ca2a63d9..ec85c03e 100644 --- a/tests/unit/parser/helpers/song.spec.js +++ b/tests/unit/parser/helpers/song.spec.js @@ -42,12 +42,15 @@ D D I just want your extra time and your...Kiss `; const parsed = parseSong(song); - const applied = forEachChordInSong(parsed.allLines, chord => chord.applied = true); + const applied = forEachChordInSong( + parsed.allLines, + (chord) => (chord.applied = true) + ); - applied.forEach(line => { + applied.forEach((line) => { if (line.type === 'chord') { - line.model.allBars.forEach(bar => { - bar.allChords.forEach(chord => { + line.model.allBars.forEach((bar) => { + bar.allChords.forEach((chord) => { expect(chord.applied).toBe(true); }); }); @@ -56,7 +59,6 @@ I just want your extra time and your...Kiss }); }); - describe('forEachChordInChordLine', () => { test('Module', () => { expect(forEachChordInChordLine).toBeInstanceOf(Function); @@ -75,17 +77,19 @@ describe('forEachChordInChordLine', () => { const chordLine = 'Am... G/B. C'; const parsed = parseChordLine(chordLine); - const applied = forEachChordInChordLine(parsed, chord => chord.applied = true); + const applied = forEachChordInChordLine( + parsed, + (chord) => (chord.applied = true) + ); - applied.allBars.forEach(bar => { - bar.allChords.forEach(chord => { + applied.allBars.forEach((bar) => { + bar.allChords.forEach((chord) => { expect(chord.applied).toBe(true); }); }); }); }); - describe('getNthOfLabel', () => { test('Module', () => { expect(getNthOfLabel).toBeInstanceOf(Function); @@ -112,15 +116,37 @@ Verse3-line3 Verse3-line4`; const parsed = parseSong(song); - const v1 = ['Verse1-line1', 'Verse1-line2', 'Verse1-line3', 'Verse1-line4', '']; - const v2 = ['Verse2-line1', 'Verse2-line2', 'Verse2-line3', 'Verse2-line4', '']; - const v3 = ['Verse3-line1', 'Verse3-line2', 'Verse3-line3', 'Verse3-line4']; + const v1 = [ + 'Verse1-line1', + 'Verse1-line2', + 'Verse1-line3', + 'Verse1-line4', + '', + ]; + const v2 = [ + 'Verse2-line1', + 'Verse2-line2', + 'Verse2-line3', + 'Verse2-line4', + '', + ]; + const v3 = [ + 'Verse3-line1', + 'Verse3-line2', + 'Verse3-line3', + 'Verse3-line4', + ]; expect(getNthOfLabel(parsed.allLines, 'v', 0)).toEqual([]); - expect(getNthOfLabel(parsed.allLines, 'v', 1).map(line => line.string)).toEqual(v1); - expect(getNthOfLabel(parsed.allLines, 'v', 2).map(line => line.string)).toEqual(v2); - expect(getNthOfLabel(parsed.allLines, 'v', 3).map(line => line.string)).toEqual(v3); + expect( + getNthOfLabel(parsed.allLines, 'v', 1).map((line) => line.string) + ).toEqual(v1); + expect( + getNthOfLabel(parsed.allLines, 'v', 2).map((line) => line.string) + ).toEqual(v2); + expect( + getNthOfLabel(parsed.allLines, 'v', 3).map((line) => line.string) + ).toEqual(v3); expect(getNthOfLabel(parsed.allLines, 'v', 4)).toEqual([]); - }); }); diff --git a/tests/unit/parser/matchers/isChord.spec.js b/tests/unit/parser/matchers/isChord.spec.js index db044b90..3901809f 100644 --- a/tests/unit/parser/matchers/isChord.spec.js +++ b/tests/unit/parser/matchers/isChord.spec.js @@ -7,24 +7,22 @@ describe('isChord', () => { }); describe.each([ + ['A', true], + ['Am7b5', true], + ['A+7', true], + ['A9', true], + ['AM7', true], + ['Asus4', true], + ['Ah', true], + ['H', true], + ['G75-', true], - [ 'A', true], - [ 'Am7b5', true], - [ 'A+7', true], - [ 'A9', true], - [ 'AM7', true], - [ 'Asus4', true], - [ 'Ah', true], - [ 'H', true], - [ 'G75-', true], - - [ undefined,false], - [ {}, false], - [ '', false], - [ 'I', false], - [ 'Amm', false], - [ '5/4', false], - + [undefined, false], + [{}, false], + ['', false], + ['I', false], + ['Amm', false], + ['5/4', false], ])('When given %s', (input, output) => { test('Properly recognize as chord', () => { expect(isChord(input)).toEqual(output); diff --git a/tests/unit/parser/matchers/isChordLine.spec.js b/tests/unit/parser/matchers/isChordLine.spec.js index 5c0ac270..36026d9b 100644 --- a/tests/unit/parser/matchers/isChordLine.spec.js +++ b/tests/unit/parser/matchers/isChordLine.spec.js @@ -7,35 +7,33 @@ describe('isChordLine', () => { }); describe.each([ + ['A', true], + ['A B', true], + ['A B', true], + [' A B ', true], + [' A. C.. ', true], + ['Am7 CM7 F+ C/G', true], + ['A. C..', true], // with 1 tab + ['A C', true], // with 2 tabs + ['A C', true], // with tab + space + tav + ['A /', true], + ['A ///', true], + ['A.. B.. /', true], + ['A.. B.. ///', true], - [ 'A', true ], - [ 'A B', true ], - [ 'A B', true ], - [ ' A B ', true ], - [ ' A. C.. ', true ], - [ 'Am7 CM7 F+ C/G', true ], - [ 'A. C..', true ], // with 1 tab - [ 'A C', true ], // with 2 tabs - [ 'A C', true ], // with tab + space + tav - [ 'A /', true ], - [ 'A ///', true ], - [ 'A.. B.. /', true ], - [ 'A.. B.. ///', true ], - - [ undefined, false ], - [ '', false ], - [ 'AB ', false ], - [ 'A X ', false ], - [ 'A C/R ', false ], - [ ' .A ..C ', false ], - [ ' .A C./F ', false ], - [ 'A | B', false ], - [ '/', false ], - [ '/ A', false ], - [ '/..', false ], - [ 'A B /.', false ], - [ '5/4\n', false ], - + [undefined, false], + ['', false], + ['AB ', false], + ['A X ', false], + ['A C/R ', false], + [' .A ..C ', false], + [' .A C./F ', false], + ['A | B', false], + ['/', false], + ['/ A', false], + ['/..', false], + ['A B /.', false], + ['5/4\n', false], ])('Test Chord line %s', (line, output) => { test('Correctly detect chord line', () => { expect(isChordLine(line)).toEqual(output); diff --git a/tests/unit/parser/matchers/isChordLineRepeater.spec.js b/tests/unit/parser/matchers/isChordLineRepeater.spec.js index e69586eb..bb76538c 100644 --- a/tests/unit/parser/matchers/isChordLineRepeater.spec.js +++ b/tests/unit/parser/matchers/isChordLineRepeater.spec.js @@ -7,15 +7,13 @@ describe('isChordLineRepeater', () => { }); describe.each([ + ['/', true], + [' / ', true], + [' / ', true], - ['/', true], - [' / ', true], - [' / ', true], - - ['/..', false], - ['/ /', false], - ['/1', false], - + ['/..', false], + ['/ /', false], + ['/1', false], ])('Chord line repeater string %s', (string, result) => { test('Correctly detect chord line repeater', () => { expect(isChordLineRepeater(string)).toEqual(result); diff --git a/tests/unit/parser/matchers/isEmptyLine.spec.js b/tests/unit/parser/matchers/isEmptyLine.spec.js index 4dceb499..09714e91 100644 --- a/tests/unit/parser/matchers/isEmptyLine.spec.js +++ b/tests/unit/parser/matchers/isEmptyLine.spec.js @@ -7,17 +7,15 @@ describe('isEmptyLine', () => { }); describe.each([ + ['', true], + [' ', true], + [' ', true], + [' ', true], //tab + [' ', true], //tab + ['\t', true], + ['\t\t', true], - ['', true], - [' ', true], - [' ', true], - [' ', true], //tab - [' ', true], //tab - ['\t', true], - ['\t\t', true], - - [' . ', false], - + [' . ', false], ])('Empty line string %s', (string, result) => { test('Correctly detect empty line', () => { expect(isEmptyLine(string)).toEqual(result); diff --git a/tests/unit/parser/matchers/isSectionLabel.spec.js b/tests/unit/parser/matchers/isSectionLabel.spec.js index e6d46f12..de56b2b4 100644 --- a/tests/unit/parser/matchers/isSectionLabel.spec.js +++ b/tests/unit/parser/matchers/isSectionLabel.spec.js @@ -7,46 +7,44 @@ describe('isSectionLabel', () => { }); describe.each([ - - ['#a', true], - ['#b', true], - ['#c', true], - ['#i', true], - ['#v', true], - ['#b', true], + ['#a', true], + ['#b', true], + ['#c', true], + ['#i', true], + ['#v', true], + ['#b', true], ['#ch', true], - ['#s', true], - ['#pre',true], - ['#o', true], + ['#s', true], + ['#pre', true], + ['#o', true], ['#ad', true], - ['#other', true], - ['#a', true], + ['#other', true], + ['#a', true], ['#other', true], - [' #a', true], - ['#a ', true], + [' #a', true], + ['#a ', true], [' #a ', true], [' #a ', true], - ['#a ', true], + ['#a ', true], [' #a ', true], ['#a x2', true], ['#a x9', true], ['#a x4', true], - ['# a', false], + ['# a', false], ['#a b', false], - ['#', false], - ['# ', false], - ['##', false], - - ['#a x', false], - ['#a x0', false], - ['#a x1', false], - ['#a x2', false], - ['#a x10', false], - ['#a x-1', false], - + ['#', false], + ['# ', false], + ['##', false], + + ['#a x', false], + ['#a x0', false], + ['#a x1', false], + ['#a x2', false], + ['#a x10', false], + ['#a x-1', false], ])('Section identifier string %s', (string, result) => { test('Correctly detect section label', () => { expect(isSectionLabel(string)).toEqual(result); diff --git a/tests/unit/parser/matchers/isTimeSignatureString.spec.js b/tests/unit/parser/matchers/isTimeSignatureString.spec.js index 85e79fca..f704b4ba 100644 --- a/tests/unit/parser/matchers/isTimeSignatureString.spec.js +++ b/tests/unit/parser/matchers/isTimeSignatureString.spec.js @@ -7,28 +7,26 @@ describe('isTimeSignatureString', () => { }); describe.each([ + ['2/2', true], + ['3/2', true], - ['2/2', true], - ['3/2', true], + ['2/4', true], + ['3/4', true], + ['4/4', true], + ['5/4', true], - ['2/4', true], - ['3/4', true], - ['4/4', true], - ['5/4', true], - - ['3/8', true], - ['6/8', true], - ['9/8', true], + ['3/8', true], + ['6/8', true], + ['9/8', true], ['12/8', true], [' 4/4', false], ['4/4 ', false], - [' 4/4 ',false], + [' 4/4 ', false], - ['5/2', false], - ['3/3', false], + ['5/2', false], + ['3/3', false], ['13/8', false], - ])('Time signature string %s', (tsString, result) => { test('Correctly detect time signature', () => { expect(isTimeSignatureString(tsString)).toEqual(result); diff --git a/tests/unit/parser/parseChordLine.spec.js b/tests/unit/parser/parseChordLine.spec.js index 2449f58d..b5032236 100644 --- a/tests/unit/parser/parseChordLine.spec.js +++ b/tests/unit/parser/parseChordLine.spec.js @@ -22,374 +22,678 @@ const ts4_4 = parseTimeSignature('4/4'); const ts5_4 = parseTimeSignature('5/4'); const ts3_8 = parseTimeSignature('3/8'); -parseChord.mockImplementation(chordString => ({ symbol: chordString })); -getChordSymbol.mockImplementation(chordDef => chordDef.symbol); +parseChord.mockImplementation((chordString) => ({ symbol: chordString })); +getChordSymbol.mockImplementation((chordDef) => chordDef.symbol); describe.each([ - ['1 bar / 1 chord / 4/4', 'Cm', ts4_4, { - allBars: [ - { - allChords: [ - { string: 'Cm', model: { symbol: 'Cm' }, duration: 4, beat: 1 }, - ], - timeSignature: ts4_4, - } - ], - chordCount: 1 - }], - - ['1 bar / 2 chords / 4/4', 'Cm.. F..', ts4_4, { - allBars: [ - { - allChords: [ - { string: 'Cm..', model: { symbol: 'Cm' }, duration: 2, beat: 1 }, - { string: 'F..', model: { symbol: 'F' }, duration: 2, beat: 3 }, - ], - timeSignature: ts4_4, - } - ], - chordCount: 2 - }], - - ['1 bar / 3 chords / 4/4', 'Cm.. F. G.', ts4_4, { - allBars: [ - { - allChords: [ - { string: 'Cm..', model: { symbol: 'Cm' }, duration: 2, beat: 1 }, - { string: 'F.', model: { symbol: 'F' }, duration: 1, beat: 3 }, - { string: 'G.', model: { symbol: 'G' }, duration: 1, beat: 4 }, - ], - timeSignature: ts4_4, - } - ], - chordCount: 3 - }], - - ['1 bar / 4 chords / 4/4', 'Cm. Em7. F. G.', ts4_4, { - allBars: [ - { - allChords: [ - { string: 'Cm.', model: { symbol: 'Cm' }, duration: 1, beat: 1 }, - { string: 'Em7.', model: { symbol: 'Em7' }, duration: 1, beat: 2 }, - { string: 'F.', model: { symbol: 'F' }, duration: 1, beat: 3 }, - { string: 'G.', model: { symbol: 'G' }, duration: 1, beat: 4 }, - ], - timeSignature: ts4_4, - } - ], - chordCount: 4 - }], - - ['2 bars / 2 chords / 4/4', 'C F', ts4_4, { - allBars: [ - { - allChords: [ - { string: 'C', model: { symbol: 'C' }, duration: 4, beat: 1 }, - ], - timeSignature: ts4_4, - }, { - allChords: [ - { string: 'F', model: { symbol: 'F' }, duration: 4, beat: 1 }, - ], - timeSignature: ts4_4, - } - ], - chordCount: 2 - }], - - ['2 bars / 3 chords / 4/4', 'C F.. G..', ts4_4, { - allBars: [ - { - allChords: [ - { string: 'C', model: { symbol: 'C' }, duration: 4, beat: 1 }, - ], - timeSignature: ts4_4, - }, { - allChords: [ - { string: 'F..', model: { symbol: 'F' }, duration: 2, beat: 1 }, - { string: 'G..', model: { symbol: 'G' }, duration: 2, beat: 3 }, - ], - timeSignature: ts4_4, - } - ], - chordCount: 3 - }], - - ['2 bars / 4 chords / 4/4', 'C... Em7. F. G...', ts4_4, { - allBars: [ - { - allChords: [ - { string: 'C...', model: { symbol: 'C' }, duration: 3, beat: 1 }, - { string: 'Em7.', model: { symbol: 'Em7' }, duration: 1, beat: 4 }, - ], - timeSignature: ts4_4, - }, { - allChords: [ - { string: 'F.', model: { symbol: 'F' }, duration: 1, beat: 1 }, - { string: 'G...', model: { symbol: 'G' }, duration: 3, beat: 2 }, - ], - timeSignature: ts4_4, - } - ], - chordCount: 4 - }], - - ['3 bars / 4 chords / 4/4', 'C Em7. F... G', ts4_4, { - allBars: [ - { - allChords: [ - { string: 'C', model: { symbol: 'C' }, duration: 4, beat: 1 }, - ], - timeSignature: ts4_4, - }, { - allChords: [ - { string: 'Em7.', model: { symbol: 'Em7' }, duration: 1, beat: 1 }, - { string: 'F...', model: { symbol: 'F' }, duration: 3, beat: 2 }, - ], - timeSignature: ts4_4, - }, { - allChords: [ - { string: 'G', model: { symbol: 'G' }, duration: 4, beat: 1 }, - ], - timeSignature: ts4_4, - } - ], - chordCount: 4 - }], - - - ['1 bar / 1 chord / 3/4', 'C', ts3_4, { - allBars: [ - { - allChords: [ - { string: 'C', model: { symbol: 'C' }, duration: 3, beat: 1 }, - ], - timeSignature: ts3_4, - } - ], - chordCount: 1 - }], - - ['1 bar / 2 chords / 3/4', 'Cm. F..', ts3_4, { - allBars: [ - { - allChords: [ - { string: 'Cm.', model: { symbol: 'Cm' }, duration: 1, beat: 1 }, - { string: 'F..', model: { symbol: 'F' }, duration: 2, beat: 2 }, - ], - timeSignature: ts3_4, - } - ], - chordCount: 2 - }], - - ['1 bar / 2 chords / 3/4', 'Cm.. F.', ts3_4, { - allBars: [ - { - allChords: [ - { string: 'Cm..', model: { symbol: 'Cm' }, duration: 2, beat: 1 }, - { string: 'F.', model: { symbol: 'F' }, duration: 1, beat: 3 }, - ], - timeSignature: ts3_4, - } - ], - chordCount: 2 - }], - - ['1 bar / 3 chords / 3/4', 'Cm. F. G.', ts3_4, { - allBars: [ - { - allChords: [ - { string: 'Cm.', model: { symbol: 'Cm' }, duration: 1, beat: 1 }, - { string: 'F.', model: { symbol: 'F' }, duration: 1, beat: 2 }, - { string: 'G.', model: { symbol: 'G' }, duration: 1, beat: 3 }, - ], - timeSignature: ts3_4, - } - ], - chordCount: 3 - }], - - ['3/8 bar', 'Cm F G.', ts3_8, { - allBars: [ - { - allChords: [ - { string: 'Cm', model: { symbol: 'Cm' }, duration: 1, beat: 1 }, - ], - timeSignature: ts3_8, - }, - { - allChords: [ - { string: 'F', model: { symbol: 'F' }, duration: 1, beat: 1 }, - ], - timeSignature: ts3_8, - }, - { - allChords: [ - { string: 'G.', model: { symbol: 'G' }, duration: 1, beat: 1 }, - ], - timeSignature: ts3_8, - } - ], - chordCount: 3 - }], - - ['trim end spaces', 'Cm ', ts4_4, { - allBars: [ - { - allChords: [ - { string: 'Cm', model: { symbol: 'Cm' }, duration: 4, beat: 1 }, - ], - timeSignature: ts4_4, - } - ], - chordCount: 1 - }], - - ['trim start spaces', ' Cm', ts4_4, { - allBars: [ - { - allChords: [ - { string: 'Cm', model: { symbol: 'Cm' }, duration: 4, beat: 1 }, - ], - timeSignature: ts4_4, - } - ], - chordCount: 1 - }], - - ['trim start and end spaces', ' Cm ', ts4_4, { - allBars: [ - { - allChords: [ - { string: 'Cm', model: { symbol: 'Cm' }, duration: 4, beat: 1 }, - ], - timeSignature: ts4_4, - } - ], - chordCount: 1 - }], - - ['handle multiple spaces between chords', 'C.. B..', ts4_4, { - allBars: [ - { - allChords: [ - { string: 'C..', model: { symbol: 'C' }, duration: 2, beat: 1 }, - { string: 'B..', model: { symbol: 'B' }, duration: 2, beat: 3 }, - ], - timeSignature: ts4_4, - } - ], - chordCount: 2 - }], - - ['handle the "no-chord" symbol', 'C NC B.. NC.. D', ts4_4, { - allBars: [ - { - allChords: [ - { string: 'C', model: { symbol: 'C' }, duration: 4, beat: 1 }, - ], - timeSignature: ts4_4, - }, - { - allChords: [ - { string: 'NC', model: 'NC', duration: 4, beat: 1 }, - ], - timeSignature: ts4_4, - }, - { - allChords: [ - { string: 'B..', model: { symbol: 'B' }, duration: 2, beat: 1 }, - { string: 'NC..', model: 'NC', duration: 2, beat: 3 }, - ], - timeSignature: ts4_4, - }, - { - allChords: [ - { string: 'D', model: { symbol: 'D' }, duration: 4, beat: 1 }, - ], - timeSignature: ts4_4, - }, - ], - chordCount: 5 - }], - -])('Should parses correctly %s: %s', (title, input, timeSignature, expected) => { - test('is correctly parsed', () => { - const options = { timeSignature }; - const parsed = parseChordLine(input, options); - - expect(parsed).toEqual(expected); - }); -}); - + [ + '1 bar / 1 chord / 4/4', + 'Cm', + ts4_4, + { + allBars: [ + { + allChords: [ + { + string: 'Cm', + model: { symbol: 'Cm' }, + duration: 4, + beat: 1, + }, + ], + timeSignature: ts4_4, + }, + ], + chordCount: 1, + }, + ], + + [ + '1 bar / 2 chords / 4/4', + 'Cm.. F..', + ts4_4, + { + allBars: [ + { + allChords: [ + { + string: 'Cm..', + model: { symbol: 'Cm' }, + duration: 2, + beat: 1, + }, + { + string: 'F..', + model: { symbol: 'F' }, + duration: 2, + beat: 3, + }, + ], + timeSignature: ts4_4, + }, + ], + chordCount: 2, + }, + ], + + [ + '1 bar / 3 chords / 4/4', + 'Cm.. F. G.', + ts4_4, + { + allBars: [ + { + allChords: [ + { + string: 'Cm..', + model: { symbol: 'Cm' }, + duration: 2, + beat: 1, + }, + { + string: 'F.', + model: { symbol: 'F' }, + duration: 1, + beat: 3, + }, + { + string: 'G.', + model: { symbol: 'G' }, + duration: 1, + beat: 4, + }, + ], + timeSignature: ts4_4, + }, + ], + chordCount: 3, + }, + ], + + [ + '1 bar / 4 chords / 4/4', + 'Cm. Em7. F. G.', + ts4_4, + { + allBars: [ + { + allChords: [ + { + string: 'Cm.', + model: { symbol: 'Cm' }, + duration: 1, + beat: 1, + }, + { + string: 'Em7.', + model: { symbol: 'Em7' }, + duration: 1, + beat: 2, + }, + { + string: 'F.', + model: { symbol: 'F' }, + duration: 1, + beat: 3, + }, + { + string: 'G.', + model: { symbol: 'G' }, + duration: 1, + beat: 4, + }, + ], + timeSignature: ts4_4, + }, + ], + chordCount: 4, + }, + ], + + [ + '2 bars / 2 chords / 4/4', + 'C F', + ts4_4, + { + allBars: [ + { + allChords: [ + { + string: 'C', + model: { symbol: 'C' }, + duration: 4, + beat: 1, + }, + ], + timeSignature: ts4_4, + }, + { + allChords: [ + { + string: 'F', + model: { symbol: 'F' }, + duration: 4, + beat: 1, + }, + ], + timeSignature: ts4_4, + }, + ], + chordCount: 2, + }, + ], + + [ + '2 bars / 3 chords / 4/4', + 'C F.. G..', + ts4_4, + { + allBars: [ + { + allChords: [ + { + string: 'C', + model: { symbol: 'C' }, + duration: 4, + beat: 1, + }, + ], + timeSignature: ts4_4, + }, + { + allChords: [ + { + string: 'F..', + model: { symbol: 'F' }, + duration: 2, + beat: 1, + }, + { + string: 'G..', + model: { symbol: 'G' }, + duration: 2, + beat: 3, + }, + ], + timeSignature: ts4_4, + }, + ], + chordCount: 3, + }, + ], + + [ + '2 bars / 4 chords / 4/4', + 'C... Em7. F. G...', + ts4_4, + { + allBars: [ + { + allChords: [ + { + string: 'C...', + model: { symbol: 'C' }, + duration: 3, + beat: 1, + }, + { + string: 'Em7.', + model: { symbol: 'Em7' }, + duration: 1, + beat: 4, + }, + ], + timeSignature: ts4_4, + }, + { + allChords: [ + { + string: 'F.', + model: { symbol: 'F' }, + duration: 1, + beat: 1, + }, + { + string: 'G...', + model: { symbol: 'G' }, + duration: 3, + beat: 2, + }, + ], + timeSignature: ts4_4, + }, + ], + chordCount: 4, + }, + ], + + [ + '3 bars / 4 chords / 4/4', + 'C Em7. F... G', + ts4_4, + { + allBars: [ + { + allChords: [ + { + string: 'C', + model: { symbol: 'C' }, + duration: 4, + beat: 1, + }, + ], + timeSignature: ts4_4, + }, + { + allChords: [ + { + string: 'Em7.', + model: { symbol: 'Em7' }, + duration: 1, + beat: 1, + }, + { + string: 'F...', + model: { symbol: 'F' }, + duration: 3, + beat: 2, + }, + ], + timeSignature: ts4_4, + }, + { + allChords: [ + { + string: 'G', + model: { symbol: 'G' }, + duration: 4, + beat: 1, + }, + ], + timeSignature: ts4_4, + }, + ], + chordCount: 4, + }, + ], + + [ + '1 bar / 1 chord / 3/4', + 'C', + ts3_4, + { + allBars: [ + { + allChords: [ + { + string: 'C', + model: { symbol: 'C' }, + duration: 3, + beat: 1, + }, + ], + timeSignature: ts3_4, + }, + ], + chordCount: 1, + }, + ], + + [ + '1 bar / 2 chords / 3/4', + 'Cm. F..', + ts3_4, + { + allBars: [ + { + allChords: [ + { + string: 'Cm.', + model: { symbol: 'Cm' }, + duration: 1, + beat: 1, + }, + { + string: 'F..', + model: { symbol: 'F' }, + duration: 2, + beat: 2, + }, + ], + timeSignature: ts3_4, + }, + ], + chordCount: 2, + }, + ], + + [ + '1 bar / 2 chords / 3/4', + 'Cm.. F.', + ts3_4, + { + allBars: [ + { + allChords: [ + { + string: 'Cm..', + model: { symbol: 'Cm' }, + duration: 2, + beat: 1, + }, + { + string: 'F.', + model: { symbol: 'F' }, + duration: 1, + beat: 3, + }, + ], + timeSignature: ts3_4, + }, + ], + chordCount: 2, + }, + ], + + [ + '1 bar / 3 chords / 3/4', + 'Cm. F. G.', + ts3_4, + { + allBars: [ + { + allChords: [ + { + string: 'Cm.', + model: { symbol: 'Cm' }, + duration: 1, + beat: 1, + }, + { + string: 'F.', + model: { symbol: 'F' }, + duration: 1, + beat: 2, + }, + { + string: 'G.', + model: { symbol: 'G' }, + duration: 1, + beat: 3, + }, + ], + timeSignature: ts3_4, + }, + ], + chordCount: 3, + }, + ], + + [ + '3/8 bar', + 'Cm F G.', + ts3_8, + { + allBars: [ + { + allChords: [ + { + string: 'Cm', + model: { symbol: 'Cm' }, + duration: 1, + beat: 1, + }, + ], + timeSignature: ts3_8, + }, + { + allChords: [ + { + string: 'F', + model: { symbol: 'F' }, + duration: 1, + beat: 1, + }, + ], + timeSignature: ts3_8, + }, + { + allChords: [ + { + string: 'G.', + model: { symbol: 'G' }, + duration: 1, + beat: 1, + }, + ], + timeSignature: ts3_8, + }, + ], + chordCount: 3, + }, + ], + + [ + 'trim end spaces', + 'Cm ', + ts4_4, + { + allBars: [ + { + allChords: [ + { + string: 'Cm', + model: { symbol: 'Cm' }, + duration: 4, + beat: 1, + }, + ], + timeSignature: ts4_4, + }, + ], + chordCount: 1, + }, + ], + + [ + 'trim start spaces', + ' Cm', + ts4_4, + { + allBars: [ + { + allChords: [ + { + string: 'Cm', + model: { symbol: 'Cm' }, + duration: 4, + beat: 1, + }, + ], + timeSignature: ts4_4, + }, + ], + chordCount: 1, + }, + ], + + [ + 'trim start and end spaces', + ' Cm ', + ts4_4, + { + allBars: [ + { + allChords: [ + { + string: 'Cm', + model: { symbol: 'Cm' }, + duration: 4, + beat: 1, + }, + ], + timeSignature: ts4_4, + }, + ], + chordCount: 1, + }, + ], + + [ + 'handle multiple spaces between chords', + 'C.. B..', + ts4_4, + { + allBars: [ + { + allChords: [ + { + string: 'C..', + model: { symbol: 'C' }, + duration: 2, + beat: 1, + }, + { + string: 'B..', + model: { symbol: 'B' }, + duration: 2, + beat: 3, + }, + ], + timeSignature: ts4_4, + }, + ], + chordCount: 2, + }, + ], + + [ + 'handle the "no-chord" symbol', + 'C NC B.. NC.. D', + ts4_4, + { + allBars: [ + { + allChords: [ + { + string: 'C', + model: { symbol: 'C' }, + duration: 4, + beat: 1, + }, + ], + timeSignature: ts4_4, + }, + { + allChords: [ + { string: 'NC', model: 'NC', duration: 4, beat: 1 }, + ], + timeSignature: ts4_4, + }, + { + allChords: [ + { + string: 'B..', + model: { symbol: 'B' }, + duration: 2, + beat: 1, + }, + { string: 'NC..', model: 'NC', duration: 2, beat: 3 }, + ], + timeSignature: ts4_4, + }, + { + allChords: [ + { + string: 'D', + model: { symbol: 'D' }, + duration: 4, + beat: 1, + }, + ], + timeSignature: ts4_4, + }, + ], + chordCount: 5, + }, + ], +])( + 'Should parses correctly %s: %s', + (title, input, timeSignature, expected) => { + test('is correctly parsed', () => { + const options = { timeSignature }; + const parsed = parseChordLine(input, options); + + expect(parsed).toEqual(expected); + }); + } +); describe.each([ + ['1 chord / 1 beat / 4/4', 'Cm.', 'Cm.', 1, 1, ts4_4], + ['1 chord / 2 beats / 4/4', 'Cm..', 'Cm..', 2, 2, ts4_4], + ['1 chord / 3 beats / 4/4', 'Cm...', 'Cm...', 3, 3, ts4_4], + ['1 chord / 5 beats / 4/4', 'Cm.....', 'Cm.....', 5, 5, ts4_4], + ['1 chord / 6 beats / 4/4', 'Cm......', 'Cm......', 6, 6, ts4_4], + ['1 chord / 7 beats / 4/4', 'Cm.......', 'Cm.......', 7, 7, ts4_4], + ['2 chords / 3 beats / 4/4', 'Cm. D..', 'D..', 2, 3, ts4_4], + ['2 chords / 5 beats / 4/4', 'Cm... D..', 'D..', 2, 5, ts4_4], + ['2 chords / 6 beats / 4/4', 'Cm... D...', 'D...', 3, 6, ts4_4], + ['2 chords / 7 beats / 4/4', 'Cm... D', 'D', 4, 7, ts4_4], + ['3 chords / 3 beats / 4/4', 'C. D. E.', 'E.', 1, 3, ts4_4], + ['3 chords / 5 beats / 4/4', 'C. D.. E..', 'E..', 2, 5, ts4_4], + + ['1 chords / 4 beats / 5/4', 'C....', 'C....', 4, 4, ts5_4], + ['2 chords / 4 beats / 5/4', 'C.. D..', 'D..', 2, 4, ts5_4], + ['3 chords / 6 beats / 5/4', 'C.. D.. E..', 'E..', 2, 6, ts5_4], + ['3 chords / 7 beats / 5/4', 'C.. D... E..', 'E..', 2, 2, ts5_4], + ['3 chords / 8 beats / 5/4', 'C... D... E..', 'D...', 3, 6, ts5_4], + ['3 chords / 9 beats / 5/4', 'C... D E.', 'D', 5, 8, ts5_4], +])( + 'Throw on %s: %s', + (title, input, string, duration, currentBeatCount, timeSignature) => { + const throwingFn = () => { + parseChordLine(input, { timeSignature }); + }; + + test('Throw InvalidChordRepetitionException', () => { + expect(throwingFn).toThrow(IncorrectBeatCountException); + }); - ['1 chord / 1 beat / 4/4', 'Cm.', 'Cm.', 1, 1, ts4_4 ], - ['1 chord / 2 beats / 4/4', 'Cm..', 'Cm..', 2, 2, ts4_4 ], - ['1 chord / 3 beats / 4/4', 'Cm...', 'Cm...', 3, 3, ts4_4 ], - ['1 chord / 5 beats / 4/4', 'Cm.....', 'Cm.....', 5, 5, ts4_4 ], - ['1 chord / 6 beats / 4/4', 'Cm......', 'Cm......', 6, 6, ts4_4 ], - ['1 chord / 7 beats / 4/4', 'Cm.......', 'Cm.......',7, 7, ts4_4 ], - ['2 chords / 3 beats / 4/4', 'Cm. D..', 'D..', 2, 3, ts4_4 ], - ['2 chords / 5 beats / 4/4', 'Cm... D..', 'D..', 2, 5, ts4_4 ], - ['2 chords / 6 beats / 4/4', 'Cm... D...', 'D...', 3, 6, ts4_4 ], - ['2 chords / 7 beats / 4/4', 'Cm... D', 'D', 4, 7, ts4_4 ], - ['3 chords / 3 beats / 4/4', 'C. D. E.', 'E.', 1, 3, ts4_4 ], - ['3 chords / 5 beats / 4/4', 'C. D.. E..', 'E..', 2, 5, ts4_4 ], - - ['1 chords / 4 beats / 5/4', 'C....', 'C....', 4, 4, ts5_4 ], - ['2 chords / 4 beats / 5/4', 'C.. D..', 'D..', 2, 4, ts5_4 ], - ['3 chords / 6 beats / 5/4', 'C.. D.. E..', 'E..', 2, 6, ts5_4 ], - ['3 chords / 7 beats / 5/4', 'C.. D... E..', 'E..', 2, 2, ts5_4 ], - ['3 chords / 8 beats / 5/4', 'C... D... E..', 'D...', 3, 6, ts5_4 ], - ['3 chords / 9 beats / 5/4', 'C... D E.', 'D', 5, 8, ts5_4 ], - -])('Throw on %s: %s', (title, input, string, duration, currentBeatCount, timeSignature) => { - const throwingFn = () => { parseChordLine(input, { timeSignature }); }; - - test('Throw InvalidChordRepetitionException', () => { - expect(throwingFn).toThrow(IncorrectBeatCountException); - }); - - test('Add correct properties to exception', () => { - try { - throwingFn(); - expect(false).toBeTruthy(); - - } catch (e) { - expect(e.name).toBe('IncorrectBeatCountException'); - expect(e.string).toBe(string); - expect(e.duration).toBe(duration); - expect(e.currentBeatCount).toBe(currentBeatCount); - expect(e.beatCount).toBe(timeSignature.beatCount); - } - }); -}); - + test('Add correct properties to exception', () => { + try { + throwingFn(); + expect(false).toBeTruthy(); + } catch (e) { + expect(e.name).toBe('IncorrectBeatCountException'); + expect(e.string).toBe(string); + expect(e.duration).toBe(duration); + expect(e.currentBeatCount).toBe(currentBeatCount); + expect(e.beatCount).toBe(timeSignature.beatCount); + } + }); + } +); describe.each([ - - ['A. A...', 'A...'], - ['A.. A..', 'A..'], - ['A... A.', 'A.'], - ['A... B. C.. C. F.', 'C.'], - ['A... B. F... F.', 'F.'], - + ['A. A...', 'A...'], + ['A.. A..', 'A..'], + ['A... A.', 'A.'], + ['A... B. C.. C. F.', 'C.'], + ['A... B. F... F.', 'F.'], ])('Throw if repeated chord in a bar: %s', (input, string) => { - const throwingFn = () => { parseChordLine(input); }; + const throwingFn = () => { + parseChordLine(input); + }; test('Throw InvalidChordRepetitionException', () => { expect(throwingFn).toThrow(InvalidChordRepetitionException); }); - test('Add correct properties to exception', () => { try { throwingFn(); expect(false).toBeTruthy(); - } catch (e) { expect(e.name).toBe('InvalidChordRepetitionException'); expect(e.string).toBe(string); @@ -397,23 +701,20 @@ describe.each([ }); }); - describe.each([ - - ['A /', 'A A'], - ['A / / /', 'A A A A'], - ['A / B /', 'A A B B'], - - ['A ///', 'A A A A'], - ['A // /', 'A A A A'], - ['A / // /', 'A A A A A'], - ['A / // B / //', 'A A A A B B B B'], - + ['A /', 'A A'], + ['A / / /', 'A A A A'], + ['A / B /', 'A A B B'], + + ['A ///', 'A A A A'], + ['A // /', 'A A A A'], + ['A / // /', 'A A A A A'], + ['A / // B / //', 'A A A A B B B B'], ])('barRepeat: %s', (input, expected) => { test('should correctly repeat previous bar', () => { const parsed = parseChordLine(input); const parsedArray = []; - forEachChordInChordLine(parsed, chord => { + forEachChordInChordLine(parsed, (chord) => { parsedArray.push(chord.model.symbol); }); @@ -421,19 +722,18 @@ describe.each([ }); }); -describe.each([ - - ['/ A'], - ['// A'], - [' / A'], - [' / A'], - -])('Throw if line starts with repeatBar symbol: %s', (input) => { - const throwingFn = () => { parseChordLine(input); }; - - test('Throw Error', () => { - expect(throwingFn).toThrow(Error); - expect(throwingFn).toThrow('A chord line cannot start with the barRepeat symbol'); - }); -}); - +describe.each([['/ A'], ['// A'], [' / A'], [' / A']])( + 'Throw if line starts with repeatBar symbol: %s', + (input) => { + const throwingFn = () => { + parseChordLine(input); + }; + + test('Throw Error', () => { + expect(throwingFn).toThrow(Error); + expect(throwingFn).toThrow( + 'A chord line cannot start with the barRepeat symbol' + ); + }); + } +); diff --git a/tests/unit/parser/parseSectionLabel.spec.js b/tests/unit/parser/parseSectionLabel.spec.js index f68b159b..249b2d40 100644 --- a/tests/unit/parser/parseSectionLabel.spec.js +++ b/tests/unit/parser/parseSectionLabel.spec.js @@ -7,7 +7,6 @@ describe('parseSectionLabel', () => { }); describe.each([ - ['#A', 'A', 0], ['#B', 'B', 0], ['#C', 'C', 0], @@ -27,7 +26,6 @@ describe.each([ ['#v x2', 'v', 2], ['#c x3', 'c', 3], ['#i x9', 'i', 9], - ])('Section identifier %s => %s', (string, label, repeatTimes) => { test('Correctly gets identifier', () => { expect(parseSectionLabel(string)).toEqual({ @@ -38,17 +36,12 @@ describe.each([ }); }); - -describe.each([ - - ['#'], - ['#'], - ['#a x'], - ['#a x33'], - -])('Invalid section identifier of %s', (string) => { - test('Throws TypeError', () => { - const throwingFn = () => parseSectionLabel(string); - expect(throwingFn).toThrow(TypeError); - }); -}); +describe.each([['#'], ['#'], ['#a x'], ['#a x33']])( + 'Invalid section identifier of %s', + (string) => { + test('Throws TypeError', () => { + const throwingFn = () => parseSectionLabel(string); + expect(throwingFn).toThrow(TypeError); + }); + } +); diff --git a/tests/unit/parser/parseSong.spec.js b/tests/unit/parser/parseSong.spec.js index 88c2ad9b..087021a4 100644 --- a/tests/unit/parser/parseSong.spec.js +++ b/tests/unit/parser/parseSong.spec.js @@ -22,19 +22,16 @@ describe('parseSong()', () => { }, asArray() { return allLines; - } + }, }; }); test('Remove any html tag', () => { - const input = - '

this is a text line

'; + const input = '

this is a text line

'; const expected = { - allLines: [ - 'this is a text line' - ], - allChords: [] + allLines: ['this is a text line'], + allChords: [], }; const parsed = parseSong(input); @@ -42,21 +39,11 @@ describe('parseSong()', () => { }); test('Accept an array as input', () => { - const input = [ - 'C.. G..', - 'line1-1', - 'Am.. F..', - 'line1-2', - ]; + const input = ['C.. G..', 'line1-1', 'Am.. F..', 'line1-2']; const expected = { - allLines: [ - 'C.. G..', - 'line1-1', - 'Am.. F..', - 'line1-2', - ], - allChords: [] + allLines: ['C.. G..', 'line1-1', 'Am.. F..', 'line1-2'], + allChords: [], }; const parsed = parseSong(input); @@ -70,17 +57,11 @@ Am.. F.. line1-2`; const expected = { - allLines: [ - 'C.. G..', - 'line1-1', - 'Am.. F..', - 'line1-2', - ], - allChords: [] + allLines: ['C.. G..', 'line1-1', 'Am.. F..', 'line1-2'], + allChords: [], }; const parsed = parseSong(input); expect(parsed).toEqual(expected); }); }); - diff --git a/tests/unit/parser/parseTimeSignature.spec.js b/tests/unit/parser/parseTimeSignature.spec.js index b9a55b01..ce8016d1 100644 --- a/tests/unit/parser/parseTimeSignature.spec.js +++ b/tests/unit/parser/parseTimeSignature.spec.js @@ -7,35 +7,34 @@ describe('parseTimeSignature', () => { }); describe.each([ + ['2/2', 2, 2, 4], + ['3/2', 3, 2, 6], - ['2/2', 2, 2, 4], - ['3/2', 3, 2, 6], + ['3/4', 3, 4, 3], + ['4/4', 4, 4, 4], + ['5/4', 5, 4, 5], - ['3/4', 3, 4, 3], - ['4/4', 4, 4, 4], - ['5/4', 5, 4, 5], - - ['3/8', 3, 8, 1], - ['6/8', 6, 8, 2], - ['9/8', 9, 8, 3], + ['3/8', 3, 8, 1], + ['6/8', 6, 8, 2], + ['9/8', 9, 8, 3], ['12/8', 12, 8, 4], - ])('Time signature of %s', (string, count, value, beatCount) => { test('Correctly gets definition', () => { - expect(parseTimeSignature(string)).toEqual({ string, count, value, beatCount }); + expect(parseTimeSignature(string)).toEqual({ + string, + count, + value, + beatCount, + }); }); }); -describe.each([ - - ['2/1'], - ['3/1'], - ['5/8'], - ['13/7'], - -])('Invalid time signature of %s', (string) => { - test('Throws TypeError', () => { - const throwingFn = () => parseTimeSignature(string); - expect(throwingFn).toThrow(TypeError); - }); -}); +describe.each([['2/1'], ['3/1'], ['5/8'], ['13/7']])( + 'Invalid time signature of %s', + (string) => { + test('Throws TypeError', () => { + const throwingFn = () => parseTimeSignature(string); + expect(throwingFn).toThrow(TypeError); + }); + } +); diff --git a/tests/unit/parser/songLinesFactory.spec.js b/tests/unit/parser/songLinesFactory.spec.js index 68a8f369..ea636276 100644 --- a/tests/unit/parser/songLinesFactory.spec.js +++ b/tests/unit/parser/songLinesFactory.spec.js @@ -8,16 +8,12 @@ import parseChordLine from '../../../src/parser/parseChordLine'; import parseSectionLabel from '../../../src/parser/parseSectionLabel'; import parseTimeSignature from '../../../src/parser/parseTimeSignature'; - describe('songLinesFactory', () => { test('Module', () => { expect(songLinesFactory).toBeInstanceOf(Function); }); - describe.each([ - ['addLine'], - ['asArray'] - ])('API', (method) => { + describe.each([['addLine'], ['asArray']])('API', (method) => { test('has method ' + method, () => { const songLines = songLinesFactory(); expect(songLines[method]).toBeInstanceOf(Function); @@ -31,7 +27,7 @@ beforeEach(() => { describe('ChordLines', () => { test('Correctly detect and parses chord lines', () => { - parseChordLine.mockImplementation(chordLine => chordLine); + parseChordLine.mockImplementation((chordLine) => chordLine); const input = `C.. G.. When I find myself in times of trouble @@ -53,22 +49,22 @@ Let it be`; const expected = [ { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'When I find myself in times of trouble'}, + { type: 'text', string: 'When I find myself in times of trouble' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'Mother mary comes to me'}, + { type: 'text', string: 'Mother mary comes to me' }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'Speaking words of wisdom'}, + { type: 'text', string: 'Speaking words of wisdom' }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', string: 'Let it be'}, - { type: 'emptyLine', string: ''}, + { type: 'text', string: 'Let it be' }, + { type: 'emptyLine', string: '' }, { type: 'chord', string: 'Am.. G..', model: 'Am.. G..' }, - { type: 'text', string: 'Let it be, let it be'}, + { type: 'text', string: 'Let it be, let it be' }, { type: 'chord', string: 'C.. F..', model: 'C.. F..' }, - { type: 'text', string: 'Let it be, let it be'}, + { type: 'text', string: 'Let it be, let it be' }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'Whispers words of wisdom'}, + { type: 'text', string: 'Whispers words of wisdom' }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', string: 'Let it be'}, + { type: 'text', string: 'Let it be' }, ]; const songLines = songLinesFactory(); @@ -78,13 +74,13 @@ Let it be`; }); test('Set chordLine as text if parsing fails', () => { - parseChordLine.mockImplementation(chordLine => { throw new Error(chordLine); }); + parseChordLine.mockImplementation((chordLine) => { + throw new Error(chordLine); + }); const input = 'C. D.. E..'; - const expected = [ - { type: 'text', string: input } - ]; + const expected = [{ type: 'text', string: input }]; const songLines = songLinesFactory(); input.split('\n').forEach(songLines.addLine); @@ -93,18 +89,13 @@ Let it be`; }); }); - describe('timeSignature', () => { test('Correctly parse time signature', () => { const ts3_4 = parseTimeSignature('3/4'); const ts4_4 = parseTimeSignature('4/4'); const ts6_8 = parseTimeSignature('6/8'); - const input = [ - '6/8', - '4/4', - '3/4', - ]; + const input = ['6/8', '4/4', '3/4']; const expected = [ { type: 'timeSignature', string: '6/8', model: ts6_8 }, @@ -132,7 +123,7 @@ describe('timeSignature', () => { 'When I find myself in times of trouble', '3/4', 'D D C A', - 'Never cared for what they know' + 'Never cared for what they know', ]; const songLines = songLinesFactory(); @@ -140,15 +131,20 @@ describe('timeSignature', () => { expect(parseChordLine.mock.calls.length).toBe(3); expect(parseChordLine.mock.calls[0][0]).toEqual('Em D. C.'); - expect(parseChordLine.mock.calls[0][1]).toEqual({ timeSignature: ts6_8 }); + expect(parseChordLine.mock.calls[0][1]).toEqual({ + timeSignature: ts6_8, + }); expect(parseChordLine.mock.calls[1][0]).toEqual('C.. G..'); - expect(parseChordLine.mock.calls[1][1]).toEqual({ timeSignature: ts4_4 }); + expect(parseChordLine.mock.calls[1][1]).toEqual({ + timeSignature: ts4_4, + }); expect(parseChordLine.mock.calls[2][0]).toEqual('D D C A'); - expect(parseChordLine.mock.calls[2][1]).toEqual({ timeSignature: ts3_4 }); + expect(parseChordLine.mock.calls[2][1]).toEqual({ + timeSignature: ts3_4, + }); }); }); - describe('sectionLabels and autoRepeatChords', () => { test('correctly parse section labels', () => { const input = [ @@ -167,17 +163,95 @@ describe('sectionLabels and autoRepeatChords', () => { const sectionsParsed = input.map(parseSectionLabel); const expected = [ - { type: 'sectionLabel', string: '#i', model: sectionsParsed[0], index: 1, indexWithoutRepeats: 1, id: 'i1' }, - { type: 'sectionLabel', string: '#v', model: sectionsParsed[1], index: 1, indexWithoutRepeats: 1, id: 'v1' }, - { type: 'sectionLabel', string: '#v', model: sectionsParsed[2], index: 2, indexWithoutRepeats: 2, id: 'v2' }, - { type: 'sectionLabel', string: '#c', model: sectionsParsed[3], index: 1, indexWithoutRepeats: 1, id: 'c1' }, - { type: 'sectionLabel', string: '#v', model: sectionsParsed[4], index: 3, indexWithoutRepeats: 3, id: 'v3' }, - { type: 'sectionLabel', string: '#c', model: sectionsParsed[5], index: 2, indexWithoutRepeats: 2, id: 'c2' }, - { type: 'sectionLabel', string: '#v', model: sectionsParsed[6], index: 4, indexWithoutRepeats: 4, id: 'v4' }, - { type: 'sectionLabel', string: '#c x2', model: sectionsParsed[7], index: 3, indexWithoutRepeats: 3, id: 'c3' }, - { type: 'sectionLabel', string: '#c x2', model: _.cloneDeep(sectionsParsed[7]), index: 4, indexWithoutRepeats: 3, id: 'c4', isFromSectionRepeat: true }, - { type: 'sectionLabel', string: '#c', model: sectionsParsed[8], index: 5, indexWithoutRepeats: 4, id: 'c5' }, - { type: 'sectionLabel', string: '#o', model: sectionsParsed[9], index: 1, indexWithoutRepeats: 1, id: 'o1' }, + { + type: 'sectionLabel', + string: '#i', + model: sectionsParsed[0], + index: 1, + indexWithoutRepeats: 1, + id: 'i1', + }, + { + type: 'sectionLabel', + string: '#v', + model: sectionsParsed[1], + index: 1, + indexWithoutRepeats: 1, + id: 'v1', + }, + { + type: 'sectionLabel', + string: '#v', + model: sectionsParsed[2], + index: 2, + indexWithoutRepeats: 2, + id: 'v2', + }, + { + type: 'sectionLabel', + string: '#c', + model: sectionsParsed[3], + index: 1, + indexWithoutRepeats: 1, + id: 'c1', + }, + { + type: 'sectionLabel', + string: '#v', + model: sectionsParsed[4], + index: 3, + indexWithoutRepeats: 3, + id: 'v3', + }, + { + type: 'sectionLabel', + string: '#c', + model: sectionsParsed[5], + index: 2, + indexWithoutRepeats: 2, + id: 'c2', + }, + { + type: 'sectionLabel', + string: '#v', + model: sectionsParsed[6], + index: 4, + indexWithoutRepeats: 4, + id: 'v4', + }, + { + type: 'sectionLabel', + string: '#c x2', + model: sectionsParsed[7], + index: 3, + indexWithoutRepeats: 3, + id: 'c3', + }, + { + type: 'sectionLabel', + string: '#c x2', + model: _.cloneDeep(sectionsParsed[7]), + index: 4, + indexWithoutRepeats: 3, + id: 'c4', + isFromSectionRepeat: true, + }, + { + type: 'sectionLabel', + string: '#c', + model: sectionsParsed[8], + index: 5, + indexWithoutRepeats: 4, + id: 'c5', + }, + { + type: 'sectionLabel', + string: '#o', + model: sectionsParsed[9], + index: 1, + indexWithoutRepeats: 1, + id: 'o1', + }, ]; const songLines = songLinesFactory(); @@ -189,7 +263,7 @@ describe('sectionLabels and autoRepeatChords', () => { test('automatically apply chords - and other lines - of previously defined identical section. Sections can contain empty lines.', () => { const ts4_4 = parseTimeSignature('4/4'); - parseChordLine.mockImplementation(chordLine => chordLine); + parseChordLine.mockImplementation((chordLine) => chordLine); const input = `#v 4/4 @@ -234,60 +308,165 @@ Whispers words of wisdom Let it be`; const expected = [ - { type: 'sectionLabel', string: '#v', index: 1, indexWithoutRepeats: 1, model: parseSectionLabel('#v'), id: 'v1' }, + { + type: 'sectionLabel', + string: '#v', + index: 1, + indexWithoutRepeats: 1, + model: parseSectionLabel('#v'), + id: 'v1', + }, { type: 'timeSignature', string: '4/4', model: _.cloneDeep(ts4_4) }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'When I find myself in times of trouble'}, + { type: 'text', string: 'When I find myself in times of trouble' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'Mother mary comes to me'}, - { type: 'emptyLine', string: ''}, + { type: 'text', string: 'Mother mary comes to me' }, + { type: 'emptyLine', string: '' }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'Speaking words of wisdom'}, + { type: 'text', string: 'Speaking words of wisdom' }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', string: 'Let it be'}, - { type: 'emptyLine', string: ''}, - { type: 'sectionLabel', string: '#v', index: 2, indexWithoutRepeats: 2, model: parseSectionLabel('#v'), id: 'v2' }, - { type: 'timeSignature', string: '4/4', model: _.cloneDeep(ts4_4), isFromAutoRepeatChords: true }, - { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromAutoRepeatChords: true }, + { type: 'text', string: 'Let it be' }, + { type: 'emptyLine', string: '' }, + { + type: 'sectionLabel', + string: '#v', + index: 2, + indexWithoutRepeats: 2, + model: parseSectionLabel('#v'), + id: 'v2', + }, + { + type: 'timeSignature', + string: '4/4', + model: _.cloneDeep(ts4_4), + isFromAutoRepeatChords: true, + }, + { + type: 'chord', + string: 'C.. G..', + model: 'C.. G..', + isFromAutoRepeatChords: true, + }, { type: 'text', string: 'And in my hour of darkness' }, - { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromAutoRepeatChords: true }, + { + type: 'chord', + string: 'Am.. F..', + model: 'Am.. F..', + isFromAutoRepeatChords: true, + }, { type: 'text', string: 'she is standing right in front of me' }, { type: 'emptyLine', string: '' }, - { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromAutoRepeatChords: true }, + { + type: 'chord', + string: 'C.. G..', + model: 'C.. G..', + isFromAutoRepeatChords: true, + }, { type: 'text', string: 'Speaking words of wisdom' }, - { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.', isFromAutoRepeatChords: true }, + { + type: 'chord', + string: 'F. Em. Dm. C.', + model: 'F. Em. Dm. C.', + isFromAutoRepeatChords: true, + }, { type: 'text', string: 'Let it be' }, { type: 'emptyLine', string: '' }, - { type: 'sectionLabel', string: '#c', index: 1, indexWithoutRepeats: 1, model: parseSectionLabel('#c'), id: 'c1' }, + { + type: 'sectionLabel', + string: '#c', + index: 1, + indexWithoutRepeats: 1, + model: parseSectionLabel('#c'), + id: 'c1', + }, { type: 'chord', string: 'Am.. G..', model: 'Am.. G..' }, - { type: 'text', string: 'Let it be, let it be'}, + { type: 'text', string: 'Let it be, let it be' }, { type: 'chord', string: 'C.. F..', model: 'C.. F..' }, - { type: 'text', string: 'Let it be, let it be'}, + { type: 'text', string: 'Let it be, let it be' }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'Whispers words of wisdom'}, + { type: 'text', string: 'Whispers words of wisdom' }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', string: 'Let it be'}, - { type: 'emptyLine', string: ''}, - { type: 'sectionLabel', string: '#v', index: 3, indexWithoutRepeats: 3, model: parseSectionLabel('#v'), id: 'v3' }, - { type: 'timeSignature', string: '4/4', model: _.cloneDeep(ts4_4), isFromAutoRepeatChords: true }, - { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromAutoRepeatChords: true }, - { type: 'text', string: 'And when the broken hearted people', }, - { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromAutoRepeatChords: true }, + { type: 'text', string: 'Let it be' }, + { type: 'emptyLine', string: '' }, + { + type: 'sectionLabel', + string: '#v', + index: 3, + indexWithoutRepeats: 3, + model: parseSectionLabel('#v'), + id: 'v3', + }, + { + type: 'timeSignature', + string: '4/4', + model: _.cloneDeep(ts4_4), + isFromAutoRepeatChords: true, + }, + { + type: 'chord', + string: 'C.. G..', + model: 'C.. G..', + isFromAutoRepeatChords: true, + }, + { type: 'text', string: 'And when the broken hearted people' }, + { + type: 'chord', + string: 'Am.. F..', + model: 'Am.. F..', + isFromAutoRepeatChords: true, + }, { type: 'text', string: 'Living in the world agree' }, { type: 'emptyLine', string: '' }, - { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromAutoRepeatChords: true }, + { + type: 'chord', + string: 'C.. G..', + model: 'C.. G..', + isFromAutoRepeatChords: true, + }, { type: 'text', string: 'There will be an answer' }, - { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.', isFromAutoRepeatChords: true }, + { + type: 'chord', + string: 'F. Em. Dm. C.', + model: 'F. Em. Dm. C.', + isFromAutoRepeatChords: true, + }, { type: 'text', string: 'Let it be' }, { type: 'emptyLine', string: '' }, - { type: 'sectionLabel', string: '#c', index: 2, indexWithoutRepeats: 2, model: parseSectionLabel('#c'), id: 'c2' }, - { type: 'chord', string: 'Am.. G..', model: 'Am.. G..', isFromAutoRepeatChords: true }, - { type: 'text', string: 'Let it be, let it be', }, - { type: 'chord', string: 'C.. F..', model: 'C.. F..', isFromAutoRepeatChords: true }, + { + type: 'sectionLabel', + string: '#c', + index: 2, + indexWithoutRepeats: 2, + model: parseSectionLabel('#c'), + id: 'c2', + }, + { + type: 'chord', + string: 'Am.. G..', + model: 'Am.. G..', + isFromAutoRepeatChords: true, + }, + { type: 'text', string: 'Let it be, let it be' }, + { + type: 'chord', + string: 'C.. F..', + model: 'C.. F..', + isFromAutoRepeatChords: true, + }, { type: 'text', string: 'Let it be, let it be' }, - { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromAutoRepeatChords: true }, + { + type: 'chord', + string: 'C.. G..', + model: 'C.. G..', + isFromAutoRepeatChords: true, + }, { type: 'text', string: 'Whispers words of wisdom' }, - { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.', isFromAutoRepeatChords: true }, + { + type: 'chord', + string: 'F. Em. Dm. C.', + model: 'F. Em. Dm. C.', + isFromAutoRepeatChords: true, + }, { type: 'text', string: 'Let it be' }, ]; @@ -298,7 +477,7 @@ Let it be`; }); test('blueprint overflows repeat, last empty line of repeat is not "chorded"', () => { - parseChordLine.mockImplementation(chordLine => chordLine); + parseChordLine.mockImplementation((chordLine) => chordLine); const input = `#v C.. G.. @@ -323,32 +502,78 @@ line3-3 `; const expected = [ - { type: 'sectionLabel', string: '#v', index: 1, indexWithoutRepeats: 1, model: parseSectionLabel('#v'), id: 'v1' }, + { + type: 'sectionLabel', + string: '#v', + index: 1, + indexWithoutRepeats: 1, + model: parseSectionLabel('#v'), + id: 'v1', + }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1'}, + { type: 'text', string: 'line1-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'line1-2'}, + { type: 'text', string: 'line1-2' }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-3'}, + { type: 'text', string: 'line1-3' }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', string: 'line1-4'}, - { type: 'emptyLine', string: ''}, - { type: 'sectionLabel', string: '#v', index: 2, indexWithoutRepeats: 2, model: parseSectionLabel('#v'), id: 'v2' }, - { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromAutoRepeatChords: true }, - { type: 'text', string: 'line2-1'}, - { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromAutoRepeatChords: true }, - { type: 'text', string: 'line2-2'}, - { type: 'emptyLine', string: ''}, - { type: 'sectionLabel', string: '#v', index: 3, indexWithoutRepeats: 3, model: parseSectionLabel('#v'), id: 'v3' }, - { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromAutoRepeatChords: true }, - { type: 'text', string: 'line3-1'}, - { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromAutoRepeatChords: true }, - { type: 'text', string: 'line3-2'}, - { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromAutoRepeatChords: true }, - { type: 'text', string: 'line3-3'}, - { type: 'emptyLine', string: ''}, - { type: 'emptyLine', string: ''}, - { type: 'emptyLine', string: ''}, + { type: 'text', string: 'line1-4' }, + { type: 'emptyLine', string: '' }, + { + type: 'sectionLabel', + string: '#v', + index: 2, + indexWithoutRepeats: 2, + model: parseSectionLabel('#v'), + id: 'v2', + }, + { + type: 'chord', + string: 'C.. G..', + model: 'C.. G..', + isFromAutoRepeatChords: true, + }, + { type: 'text', string: 'line2-1' }, + { + type: 'chord', + string: 'Am.. F..', + model: 'Am.. F..', + isFromAutoRepeatChords: true, + }, + { type: 'text', string: 'line2-2' }, + { type: 'emptyLine', string: '' }, + { + type: 'sectionLabel', + string: '#v', + index: 3, + indexWithoutRepeats: 3, + model: parseSectionLabel('#v'), + id: 'v3', + }, + { + type: 'chord', + string: 'C.. G..', + model: 'C.. G..', + isFromAutoRepeatChords: true, + }, + { type: 'text', string: 'line3-1' }, + { + type: 'chord', + string: 'Am.. F..', + model: 'Am.. F..', + isFromAutoRepeatChords: true, + }, + { type: 'text', string: 'line3-2' }, + { + type: 'chord', + string: 'C.. G..', + model: 'C.. G..', + isFromAutoRepeatChords: true, + }, + { type: 'text', string: 'line3-3' }, + { type: 'emptyLine', string: '' }, + { type: 'emptyLine', string: '' }, + { type: 'emptyLine', string: '' }, ]; const songLines = songLinesFactory(); @@ -358,7 +583,7 @@ line3-3 }); test('repeat overflows blueprint', () => { - parseChordLine.mockImplementation(chordLine => chordLine); + parseChordLine.mockImplementation((chordLine) => chordLine); const input = `#v C.. G.. @@ -375,21 +600,45 @@ F. Em. Dm. C. line2-4`; const expected = [ - { type: 'sectionLabel', string: '#v', index: 1, indexWithoutRepeats: 1, model: parseSectionLabel('#v'), id: 'v1' }, + { + type: 'sectionLabel', + string: '#v', + index: 1, + indexWithoutRepeats: 1, + model: parseSectionLabel('#v'), + id: 'v1', + }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1'}, + { type: 'text', string: 'line1-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'line1-2'}, - { type: 'emptyLine', string: ''}, - { type: 'sectionLabel', string: '#v', index: 2, indexWithoutRepeats: 2, model: parseSectionLabel('#v'), id: 'v2' }, - { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromAutoRepeatChords: true }, - { type: 'text', string: 'line2-1'}, - { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromAutoRepeatChords: true }, - { type: 'text', string: 'line2-2'}, + { type: 'text', string: 'line1-2' }, + { type: 'emptyLine', string: '' }, + { + type: 'sectionLabel', + string: '#v', + index: 2, + indexWithoutRepeats: 2, + model: parseSectionLabel('#v'), + id: 'v2', + }, + { + type: 'chord', + string: 'C.. G..', + model: 'C.. G..', + isFromAutoRepeatChords: true, + }, + { type: 'text', string: 'line2-1' }, + { + type: 'chord', + string: 'Am.. F..', + model: 'Am.. F..', + isFromAutoRepeatChords: true, + }, + { type: 'text', string: 'line2-2' }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line2-3'}, + { type: 'text', string: 'line2-3' }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', string: 'line2-4'}, + { type: 'text', string: 'line2-4' }, ]; const songLines = songLinesFactory(); @@ -399,7 +648,7 @@ line2-4`; }); test('allow chords override in repeat', () => { - parseChordLine.mockImplementation(chordLine => chordLine); + parseChordLine.mockImplementation((chordLine) => chordLine); const input = `#v C.. G.. @@ -413,17 +662,36 @@ F. Em. Dm. C. line2-2`; const expected = [ - { type: 'sectionLabel', string: '#v', index: 1, indexWithoutRepeats: 1, model: parseSectionLabel('#v'), id: 'v1' }, + { + type: 'sectionLabel', + string: '#v', + index: 1, + indexWithoutRepeats: 1, + model: parseSectionLabel('#v'), + id: 'v1', + }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1'}, + { type: 'text', string: 'line1-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'line1-2'}, - { type: 'emptyLine', string: ''}, - { type: 'sectionLabel', string: '#v', index: 2, indexWithoutRepeats: 2, model: parseSectionLabel('#v'), id: 'v2' }, - { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromAutoRepeatChords: true }, - { type: 'text', string: 'line2-1'}, + { type: 'text', string: 'line1-2' }, + { type: 'emptyLine', string: '' }, + { + type: 'sectionLabel', + string: '#v', + index: 2, + indexWithoutRepeats: 2, + model: parseSectionLabel('#v'), + id: 'v2', + }, + { + type: 'chord', + string: 'C.. G..', + model: 'C.. G..', + isFromAutoRepeatChords: true, + }, + { type: 'text', string: 'line2-1' }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', string: 'line2-2'}, + { type: 'text', string: 'line2-2' }, ]; const songLines = songLinesFactory(); @@ -435,7 +703,7 @@ line2-2`; describe('chordLineRepeater', () => { test('should allow to repeat last chordLine', () => { - parseChordLine.mockImplementation(chordLine => chordLine); + parseChordLine.mockImplementation((chordLine) => chordLine); const input = `C.. G.. line1-1 @@ -448,13 +716,23 @@ line2-1`; const expected = [ { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1'}, - { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromChordLineRepeater: true }, - { type: 'text', string: 'line1-2'}, - { type: 'emptyLine', string: ''}, - { type: 'emptyLine', string: ''}, - { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromChordLineRepeater: true }, - { type: 'text', string: 'line2-1'}, + { type: 'text', string: 'line1-1' }, + { + type: 'chord', + string: 'C.. G..', + model: 'C.. G..', + isFromChordLineRepeater: true, + }, + { type: 'text', string: 'line1-2' }, + { type: 'emptyLine', string: '' }, + { type: 'emptyLine', string: '' }, + { + type: 'chord', + string: 'C.. G..', + model: 'C.. G..', + isFromChordLineRepeater: true, + }, + { type: 'text', string: 'line2-1' }, ]; const songLines = songLinesFactory(); @@ -464,7 +742,7 @@ line2-1`; }); test('should be parsed as text line if there is no chordLine before', () => { - parseChordLine.mockImplementation(chordLine => chordLine); + parseChordLine.mockImplementation((chordLine) => chordLine); const input = `/ line1-1 @@ -474,12 +752,12 @@ line1-2 line1-3`; const expected = [ - { type: 'text', string: '/'}, - { type: 'text', string: 'line1-1'}, - { type: 'text', string: '/'}, - { type: 'text', string: 'line1-2'}, - { type: 'text', string: '/'}, - { type: 'text', string: 'line1-3'}, + { type: 'text', string: '/' }, + { type: 'text', string: 'line1-1' }, + { type: 'text', string: '/' }, + { type: 'text', string: 'line1-2' }, + { type: 'text', string: '/' }, + { type: 'text', string: 'line1-3' }, ]; const songLines = songLinesFactory(); @@ -489,7 +767,7 @@ line1-3`; }); test('should be usable in repeated section', () => { - parseChordLine.mockImplementation(chordLine => chordLine); + parseChordLine.mockImplementation((chordLine) => chordLine); const input = `#v C.. G.. @@ -503,17 +781,41 @@ line2-1 line2-2`; const expected = [ - { type: 'sectionLabel', string: '#v', index: 1, indexWithoutRepeats: 1, model: parseSectionLabel('#v'), id: 'v1' }, + { + type: 'sectionLabel', + string: '#v', + index: 1, + indexWithoutRepeats: 1, + model: parseSectionLabel('#v'), + id: 'v1', + }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1'}, + { type: 'text', string: 'line1-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'line1-2'}, - { type: 'emptyLine', string: ''}, - { type: 'sectionLabel', string: '#v', index: 2, indexWithoutRepeats: 2, model: parseSectionLabel('#v'), id: 'v2' }, - { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromAutoRepeatChords: true }, - { type: 'text', string: 'line2-1'}, - { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromChordLineRepeater: true }, - { type: 'text', string: 'line2-2'}, + { type: 'text', string: 'line1-2' }, + { type: 'emptyLine', string: '' }, + { + type: 'sectionLabel', + string: '#v', + index: 2, + indexWithoutRepeats: 2, + model: parseSectionLabel('#v'), + id: 'v2', + }, + { + type: 'chord', + string: 'C.. G..', + model: 'C.. G..', + isFromAutoRepeatChords: true, + }, + { type: 'text', string: 'line2-1' }, + { + type: 'chord', + string: 'C.. G..', + model: 'C.. G..', + isFromChordLineRepeater: true, + }, + { type: 'text', string: 'line2-2' }, ]; const songLines = songLinesFactory(); @@ -523,10 +825,9 @@ line2-2`; }); }); - describe('Repeat directive (x3, x5...)', () => { test('should allow to repeat section', () => { - parseChordLine.mockImplementation(chordLine => chordLine); + parseChordLine.mockImplementation((chordLine) => chordLine); const input = `#v x2 C.. G.. @@ -535,16 +836,41 @@ Am.. F.. line1-2 `; const expected = [ - { type: 'sectionLabel', string: '#v x2', index: 1, indexWithoutRepeats: 1, model: parseSectionLabel('#v x2'), id: 'v1' }, + { + type: 'sectionLabel', + string: '#v x2', + index: 1, + indexWithoutRepeats: 1, + model: parseSectionLabel('#v x2'), + id: 'v1', + }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1'}, + { type: 'text', string: 'line1-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'line1-2'}, - { type: 'emptyLine', string: ''}, - { type: 'sectionLabel', string: '#v x2', index: 2, indexWithoutRepeats: 1, model: parseSectionLabel('#v x2'), id: 'v2', isFromSectionRepeat: true }, - { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromSectionRepeat: true }, + { type: 'text', string: 'line1-2' }, + { type: 'emptyLine', string: '' }, + { + type: 'sectionLabel', + string: '#v x2', + index: 2, + indexWithoutRepeats: 1, + model: parseSectionLabel('#v x2'), + id: 'v2', + isFromSectionRepeat: true, + }, + { + type: 'chord', + string: 'C.. G..', + model: 'C.. G..', + isFromSectionRepeat: true, + }, { type: 'text', string: 'line1-1', isFromSectionRepeat: true }, - { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromSectionRepeat: true }, + { + type: 'chord', + string: 'Am.. F..', + model: 'Am.. F..', + isFromSectionRepeat: true, + }, { type: 'text', string: 'line1-2', isFromSectionRepeat: true }, { type: 'emptyLine', string: '', isFromSectionRepeat: true }, ]; @@ -556,7 +882,7 @@ line1-2 }); test('should allow to repeat section that contains other kinds of repeats', () => { - parseChordLine.mockImplementation(chordLine => chordLine); + parseChordLine.mockImplementation((chordLine) => chordLine); const input = `#v C.. G.. @@ -571,28 +897,83 @@ line2-2 line2-3 `; const expected = [ - { type: 'sectionLabel', string: '#v', index: 1, indexWithoutRepeats: 1, model: parseSectionLabel('#v'), id: 'v1' }, + { + type: 'sectionLabel', + string: '#v', + index: 1, + indexWithoutRepeats: 1, + model: parseSectionLabel('#v'), + id: 'v1', + }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1'}, + { type: 'text', string: 'line1-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'line1-2'}, - { type: 'emptyLine', string: ''}, - { type: 'sectionLabel', string: '#v x2', index: 2, indexWithoutRepeats: 2, model: parseSectionLabel('#v x2'), id: 'v2' }, - { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromAutoRepeatChords: true }, - { type: 'text', string: 'line2-1'}, - { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromAutoRepeatChords: true }, - { type: 'text', string: 'line2-2'}, - { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromChordLineRepeater: true }, - { type: 'text', string: 'line2-3'}, - { type: 'emptyLine', string: ''}, - { type: 'sectionLabel', string: '#v x2', index: 3, indexWithoutRepeats: 2, model: parseSectionLabel('#v x2'), id: 'v3', isFromSectionRepeat: true }, - { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromSectionRepeat: true, isFromAutoRepeatChords: true }, + { type: 'text', string: 'line1-2' }, + { type: 'emptyLine', string: '' }, + { + type: 'sectionLabel', + string: '#v x2', + index: 2, + indexWithoutRepeats: 2, + model: parseSectionLabel('#v x2'), + id: 'v2', + }, + { + type: 'chord', + string: 'C.. G..', + model: 'C.. G..', + isFromAutoRepeatChords: true, + }, + { type: 'text', string: 'line2-1' }, + { + type: 'chord', + string: 'Am.. F..', + model: 'Am.. F..', + isFromAutoRepeatChords: true, + }, + { type: 'text', string: 'line2-2' }, + { + type: 'chord', + string: 'Am.. F..', + model: 'Am.. F..', + isFromChordLineRepeater: true, + }, + { type: 'text', string: 'line2-3' }, + { type: 'emptyLine', string: '' }, + { + type: 'sectionLabel', + string: '#v x2', + index: 3, + indexWithoutRepeats: 2, + model: parseSectionLabel('#v x2'), + id: 'v3', + isFromSectionRepeat: true, + }, + { + type: 'chord', + string: 'C.. G..', + model: 'C.. G..', + isFromSectionRepeat: true, + isFromAutoRepeatChords: true, + }, { type: 'text', string: 'line2-1', isFromSectionRepeat: true }, - { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromSectionRepeat: true, isFromAutoRepeatChords: true }, + { + type: 'chord', + string: 'Am.. F..', + model: 'Am.. F..', + isFromSectionRepeat: true, + isFromAutoRepeatChords: true, + }, { type: 'text', string: 'line2-2', isFromSectionRepeat: true }, - { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromSectionRepeat: true, isFromChordLineRepeater: true }, + { + type: 'chord', + string: 'Am.. F..', + model: 'Am.. F..', + isFromSectionRepeat: true, + isFromChordLineRepeater: true, + }, { type: 'text', string: 'line2-3', isFromSectionRepeat: true }, - { type: 'emptyLine', string: '', isFromSectionRepeat: true}, + { type: 'emptyLine', string: '', isFromSectionRepeat: true }, ]; const songLines = songLinesFactory(); diff --git a/tests/unit/renderer/components/renderBarContent.spec.js b/tests/unit/renderer/components/renderBarContent.spec.js index 02c21660..a16d6b64 100644 --- a/tests/unit/renderer/components/renderBarContent.spec.js +++ b/tests/unit/renderer/components/renderBarContent.spec.js @@ -10,7 +10,7 @@ import htmlToElement from '../../../../src/core/dom/htmlToElement'; import { forEachChordInChordLine } from '../../../../src/parser/helper/songs'; -renderChordSymbol.mockImplementation(chordSymbol => chordSymbol); +renderChordSymbol.mockImplementation((chordSymbol) => chordSymbol); describe('renderBarContent', () => { test('Module', () => { @@ -19,7 +19,10 @@ describe('renderBarContent', () => { test('Should return valid html', () => { let parsed = parseChordLine('C.. G. F.'); - parsed = forEachChordInChordLine(parsed, chord => chord.symbol = getChordSymbol(chord.model)); + parsed = forEachChordInChordLine( + parsed, + (chord) => (chord.symbol = getChordSymbol(chord.model)) + ); const rendered = renderBarContent(parsed.allBars[0]); const element = htmlToElement(rendered); @@ -31,20 +34,21 @@ describe('renderBarContent', () => { }); describe.each([ - - ['1 bar / 1 chord / 4 bpb', 'C', 'C '], - ['1 bar / 2 chords / 4 bpb (1/3)', 'C. G...', 'C G '], - ['1 bar / 2 chords / 4 bpb (2/2)', 'C.. G..', 'C G '], - ['1 bar / 2 chords / 4 bpb (3/1)', 'C... G.', 'C G '], - ['1 bar / 3 chords / 4 bpb (1/1/2)', 'C. G. F..', 'C G F '], - ['1 bar / 3 chords / 4 bpb (1/2/1)', 'C. G.. F.', 'C G F '], - ['1 bar / 3 chords / 4 bpb (2/1/1)', 'C.. G. F.', 'C G F '], - ['1 bar / 4 chords / 4 bpb (1/1/1/1)', 'C. G. F. Am.', 'C G F Ami '], - + ['1 bar / 1 chord / 4 bpb', 'C', 'C '], + ['1 bar / 2 chords / 4 bpb (1/3)', 'C. G...', 'C G '], + ['1 bar / 2 chords / 4 bpb (2/2)', 'C.. G..', 'C G '], + ['1 bar / 2 chords / 4 bpb (3/1)', 'C... G.', 'C G '], + ['1 bar / 3 chords / 4 bpb (1/1/2)', 'C. G. F..', 'C G F '], + ['1 bar / 3 chords / 4 bpb (1/2/1)', 'C. G.. F.', 'C G F '], + ['1 bar / 3 chords / 4 bpb (2/1/1)', 'C.. G. F.', 'C G F '], + ['1 bar / 4 chords / 4 bpb (1/1/1/1)', 'C. G. F. Am.', 'C G F Ami '], ])('%s: %s', (title, input, output) => { test('Renders with default spacing: ' + output, () => { let parsed = parseChordLine(input); - parsed = forEachChordInChordLine(parsed, chord => chord.symbol = getChordSymbol(chord.model)); + parsed = forEachChordInChordLine( + parsed, + (chord) => (chord.symbol = getChordSymbol(chord.model)) + ); const rendered = renderBarContent(parsed.allBars[0]); expect(stripTags(rendered)).toEqual(output); @@ -52,46 +56,48 @@ describe.each([ }); describe.each([ - - ['spacesAfter = 0', 'C. G. F..', 0, 'CGF'], - ['spacesAfter = 1', 'C. G. F..', 1, 'C G F '], - ['spacesAfter = 2', 'C. G. F..', 2, 'C G F '], - ['spacesAfter = 3', 'C. G. F..', 3, 'C G F '], - ['spacesAfter = 4', 'C. G. F..', 4, 'C G F '], - ['spacesAfter = 5', 'C. G. F..', 5, 'C G F '], - ['spacesAfter = 6', 'C. G. F..', 6, 'C G F '], - + ['spacesAfter = 0', 'C. G. F..', 0, 'CGF'], + ['spacesAfter = 1', 'C. G. F..', 1, 'C G F '], + ['spacesAfter = 2', 'C. G. F..', 2, 'C G F '], + ['spacesAfter = 3', 'C. G. F..', 3, 'C G F '], + ['spacesAfter = 4', 'C. G. F..', 4, 'C G F '], + ['spacesAfter = 5', 'C. G. F..', 5, 'C G F '], + ['spacesAfter = 6', 'C. G. F..', 6, 'C G F '], ])('%s: %s', (title, input, spacesAfter, output) => { test('Respect spacesAfter value: ' + output, () => { let parsed = parseChordLine(input); - parsed = forEachChordInChordLine(parsed, chord => chord.symbol = getChordSymbol(chord.model)); + parsed = forEachChordInChordLine( + parsed, + (chord) => (chord.symbol = getChordSymbol(chord.model)) + ); - parsed.allBars[0].allChords.forEach(chord => { + parsed.allBars[0].allChords.forEach((chord) => { chord.spacesAfter = spacesAfter; }); - const rendered = renderBarContent(parsed.allBars[0] ); + const rendered = renderBarContent(parsed.allBars[0]); expect(stripTags(rendered)).toEqual(output); }); }); describe.each([ - - ['spacesWithin = 0', 'C. G. F..', 0, 'C G F '], - ['spacesWithin = 1', 'C. G. F..', 1, 'C G F '], - ['spacesWithin = 2', 'C. G. F..', 2, 'C G F '], - ['spacesWithin = 3', 'C. G. F..', 3, 'C G F '], - ['spacesWithin = 4', 'C. G. F..', 4, 'C G F '], - ['spacesWithin = 5', 'C. G. F..', 5, 'C G F '], - ['spacesWithin = 6', 'C. G. F..', 6, 'C G F '], - + ['spacesWithin = 0', 'C. G. F..', 0, 'C G F '], + ['spacesWithin = 1', 'C. G. F..', 1, 'C G F '], + ['spacesWithin = 2', 'C. G. F..', 2, 'C G F '], + ['spacesWithin = 3', 'C. G. F..', 3, 'C G F '], + ['spacesWithin = 4', 'C. G. F..', 4, 'C G F '], + ['spacesWithin = 5', 'C. G. F..', 5, 'C G F '], + ['spacesWithin = 6', 'C. G. F..', 6, 'C G F '], ])('%s: %s', (title, input, spacesWithin, output) => { test('Respect spacesWithin value: ' + output, () => { let parsed = parseChordLine(input); - parsed = forEachChordInChordLine(parsed, chord => chord.symbol = getChordSymbol(chord.model)); + parsed = forEachChordInChordLine( + parsed, + (chord) => (chord.symbol = getChordSymbol(chord.model)) + ); - parsed.allBars[0].allChords.forEach(chord => { + parsed.allBars[0].allChords.forEach((chord) => { chord.spacesWithin = spacesWithin; }); diff --git a/tests/unit/renderer/components/renderChordLine.spec.js b/tests/unit/renderer/components/renderChordLine.spec.js index 23dc22d2..71d2609c 100644 --- a/tests/unit/renderer/components/renderChordLine.spec.js +++ b/tests/unit/renderer/components/renderChordLine.spec.js @@ -27,15 +27,12 @@ describe('chordLine renderer', () => { }); describe.each([ - - ['A B C', '|A|B|C|'], - ['A.. B.. C.. D..', '|A B|C D|'], - + ['A B C', '|A|B|C|'], + ['A.. B.. C.. D..', '|A B|C D|'], ])('Render chordLine "%s" as "%s"', (input, output) => { test('expected rendering', () => { - renderBarContent.mockImplementation(bar => bar.allChords - .map(chord => getChordSymbol(chord.model)) - .join(' ') + renderBarContent.mockImplementation((bar) => + bar.allChords.map((chord) => getChordSymbol(chord.model)).join(' ') ); const chordLine = parseChordLine(input); diff --git a/tests/unit/renderer/components/renderChordSymbol.spec.js b/tests/unit/renderer/components/renderChordSymbol.spec.js index 88d62288..347d9ee8 100644 --- a/tests/unit/renderer/components/renderChordSymbol.spec.js +++ b/tests/unit/renderer/components/renderChordSymbol.spec.js @@ -19,10 +19,8 @@ describe('chordSymbol renderer', () => { }); describe.each([ - - ['A', 'A'], - ['AM7', 'AM7'], - + ['A', 'A'], + ['AM7', 'AM7'], ])('Render chord %s as %s', (input, output) => { test('expected rendering', () => { const rendered = renderChordSymbol(input); diff --git a/tests/unit/renderer/components/renderEmptyLine.spec.js b/tests/unit/renderer/components/renderEmptyLine.spec.js index 630d506f..8e335a8e 100644 --- a/tests/unit/renderer/components/renderEmptyLine.spec.js +++ b/tests/unit/renderer/components/renderEmptyLine.spec.js @@ -16,7 +16,6 @@ describe('renderEmptyLine', () => { }); }); - describe('Behaviour', () => { test('renders empty line as non-breaking space', () => { const rendered = renderEmptyLine(); diff --git a/tests/unit/renderer/components/renderLine.spec.js b/tests/unit/renderer/components/renderLine.spec.js index 3899457a..db71ea13 100644 --- a/tests/unit/renderer/components/renderLine.spec.js +++ b/tests/unit/renderer/components/renderLine.spec.js @@ -17,15 +17,57 @@ describe('renderLine', () => { }); describe.each([ + [ + 'none', + { + isFromSectionRepeat: false, + isFromAutoRepeatChords: false, + isFromChordLineRepeater: false, + }, + [], + ], - ['none', { isFromSectionRepeat: false, isFromAutoRepeatChords: false, isFromChordLineRepeater: false }, []], - - ['isFromSectionRepeat', { isFromSectionRepeat: true, isFromAutoRepeatChords: false, isFromChordLineRepeater: false }, ['cmLine--isFromSectionRepeat']], - ['isFromAutoRepeatChords', { isFromSectionRepeat: false, isFromAutoRepeatChords: true, isFromChordLineRepeater: false }, ['cmLine--isFromAutoRepeatChords']], - ['isFromChordLineRepeater', { isFromSectionRepeat: false, isFromAutoRepeatChords: false, isFromChordLineRepeater: true }, ['cmLine--isFromChordLineRepeater']], - - ['all', { isFromSectionRepeat: true, isFromAutoRepeatChords: true, isFromChordLineRepeater: true }, ['cmLine--isFromSectionRepeat', 'cmLine--isFromAutoRepeatChords', 'cmLine--isFromChordLineRepeater']], + [ + 'isFromSectionRepeat', + { + isFromSectionRepeat: true, + isFromAutoRepeatChords: false, + isFromChordLineRepeater: false, + }, + ['cmLine--isFromSectionRepeat'], + ], + [ + 'isFromAutoRepeatChords', + { + isFromSectionRepeat: false, + isFromAutoRepeatChords: true, + isFromChordLineRepeater: false, + }, + ['cmLine--isFromAutoRepeatChords'], + ], + [ + 'isFromChordLineRepeater', + { + isFromSectionRepeat: false, + isFromAutoRepeatChords: false, + isFromChordLineRepeater: true, + }, + ['cmLine--isFromChordLineRepeater'], + ], + [ + 'all', + { + isFromSectionRepeat: true, + isFromAutoRepeatChords: true, + isFromChordLineRepeater: true, + }, + [ + 'cmLine--isFromSectionRepeat', + 'cmLine--isFromAutoRepeatChords', + 'cmLine--isFromChordLineRepeater', + ], + ], ])('repeat identifier classes', (title, options, expected) => { test('correctly adds expected classes', () => { const rendered = renderLine({ line: 'myLine' }, options); @@ -33,7 +75,7 @@ describe.each([ expect.assertions(expected.length + 1); expect(element).toBeInstanceOf(Node); - expected.forEach(className => { + expected.forEach((className) => { expect(element.classList.contains(className)).toBe(true); }); }); diff --git a/tests/unit/renderer/components/renderSectionLabel.spec.js b/tests/unit/renderer/components/renderSectionLabel.spec.js index 7593d591..8b9671a1 100644 --- a/tests/unit/renderer/components/renderSectionLabel.spec.js +++ b/tests/unit/renderer/components/renderSectionLabel.spec.js @@ -23,15 +23,15 @@ describe('renderSectionLabel', () => { describe('Shortcuts and case', () => { describe.each([ - [ '#a', 'Adlib' ], - [ '#b', 'Bridge' ], - [ '#c', 'Chorus' ], - [ '#i', 'Intro' ], - [ '#o', 'Outro' ], - [ '#p', 'Pre-chorus' ], - [ '#s', 'Solo' ], - [ '#u', 'Interlude' ], - [ '#v', 'Verse' ], + ['#a', 'Adlib'], + ['#b', 'Bridge'], + ['#c', 'Chorus'], + ['#i', 'Intro'], + ['#o', 'Outro'], + ['#p', 'Pre-chorus'], + ['#s', 'Solo'], + ['#u', 'Interlude'], + ['#v', 'Verse'], ])('Should replace shortcuts', (string, output) => { test('expands ' + string + ' to ' + output, () => { const line = { @@ -47,88 +47,106 @@ describe('Shortcuts and case', () => { }); describe.each([ - [ '#inter', 'Inter' ], - [ '#special', 'Special' ], - [ '#other', 'Other' ], - ])('Should render custom sections with a capital first letter', (string, output) => { - test('renders ' + string + ' to ' + output, () => { - const line = { - model: parseSectionLabel(string), - index: 1, - }; - const rendered = renderSectionLabel(line); - const element = htmlToElement(rendered); - - expect(element).toBeInstanceOf(Node); - expect(element.innerHTML).toBe(output); - }); - }); + ['#inter', 'Inter'], + ['#special', 'Special'], + ['#other', 'Other'], + ])( + 'Should render custom sections with a capital first letter', + (string, output) => { + test('renders ' + string + ' to ' + output, () => { + const line = { + model: parseSectionLabel(string), + index: 1, + }; + const rendered = renderSectionLabel(line); + const element = htmlToElement(rendered); + + expect(element).toBeInstanceOf(Node); + expect(element.innerHTML).toBe(output); + }); + } + ); }); - describe('Label indexes', () => { describe.each([ - [ '#v', 5, { v: 10 }, 'Verse 5' ], - [ '#c', 3, { c: 5 }, 'Chorus 3' ], - [ '#b', 7, { b: 8 }, 'Bridge 7' ], - ])('Should append index to section label if expandSectionRepeats === true', (string, index, sectionsStats, output) => { - test('appends ' + index, () => { - const line = { - model: parseSectionLabel(string), - index, - }; - const rendered = renderSectionLabel(line, { sectionsStats, expandSectionRepeats: true }); - const element = htmlToElement(rendered); - - expect(element).toBeInstanceOf(Node); - expect(element.innerHTML).toBe(output); - }); - }); + ['#v', 5, { v: 10 }, 'Verse 5'], + ['#c', 3, { c: 5 }, 'Chorus 3'], + ['#b', 7, { b: 8 }, 'Bridge 7'], + ])( + 'Should append index to section label if expandSectionRepeats === true', + (string, index, sectionsStats, output) => { + test('appends ' + index, () => { + const line = { + model: parseSectionLabel(string), + index, + }; + const rendered = renderSectionLabel(line, { + sectionsStats, + expandSectionRepeats: true, + }); + const element = htmlToElement(rendered); + + expect(element).toBeInstanceOf(Node); + expect(element.innerHTML).toBe(output); + }); + } + ); describe.each([ - [ '#v', 5, { v: 10 }, 'Verse 5' ], - [ '#c', 3, { c: 5 }, 'Chorus 3' ], - [ '#b', 7, { b: 8 }, 'Bridge 7' ], - ])('Should append indexWithoutRepeats if expandSectionRepeats === false', (string, indexWithoutRepeats, sectionsStats, output) => { - test('appends ' + indexWithoutRepeats, () => { - const line = { - model: parseSectionLabel(string), - indexWithoutRepeats, - }; - const rendered = renderSectionLabel(line, { sectionsStats, expandSectionRepeats: false }); - const element = htmlToElement(rendered); - - expect(element).toBeInstanceOf(Node); - expect(element.innerHTML).toBe(output); - }); - }); + ['#v', 5, { v: 10 }, 'Verse 5'], + ['#c', 3, { c: 5 }, 'Chorus 3'], + ['#b', 7, { b: 8 }, 'Bridge 7'], + ])( + 'Should append indexWithoutRepeats if expandSectionRepeats === false', + (string, indexWithoutRepeats, sectionsStats, output) => { + test('appends ' + indexWithoutRepeats, () => { + const line = { + model: parseSectionLabel(string), + indexWithoutRepeats, + }; + const rendered = renderSectionLabel(line, { + sectionsStats, + expandSectionRepeats: false, + }); + const element = htmlToElement(rendered); + + expect(element).toBeInstanceOf(Node); + expect(element.innerHTML).toBe(output); + }); + } + ); describe.each([ - [ '#v', 1, { v: 1 }, 'Verse' ], - [ '#c', 1, { c: 2 }, 'Chorus 1' ], - ])('Should not append index to section label if section is unique', (string, index, sectionsStats, output) => { - test('appends ' + index, () => { - const line = { - model: parseSectionLabel(string), - index, - }; - const rendered = renderSectionLabel(line, { sectionsStats }); - const element = htmlToElement(rendered); - - expect(element).toBeInstanceOf(Node); - expect(element.innerHTML).toBe(output); - }); - }); + ['#v', 1, { v: 1 }, 'Verse'], + ['#c', 1, { c: 2 }, 'Chorus 1'], + ])( + 'Should not append index to section label if section is unique', + (string, index, sectionsStats, output) => { + test('appends ' + index, () => { + const line = { + model: parseSectionLabel(string), + index, + }; + const rendered = renderSectionLabel(line, { sectionsStats }); + const element = htmlToElement(rendered); + + expect(element).toBeInstanceOf(Node); + expect(element.innerHTML).toBe(output); + }); + } + ); }); - describe('Repeat indications', () => { test('should NOT append repeat indication if expandSectionRepeats === true', () => { const line = { model: parseSectionLabel('#v x5'), index: 1, }; - const rendered = renderSectionLabel(line, { expandSectionRepeats: true }); + const rendered = renderSectionLabel(line, { + expandSectionRepeats: true, + }); const element = htmlToElement(rendered); expect(element).toBeInstanceOf(Node); @@ -140,7 +158,9 @@ describe('Repeat indications', () => { model: parseSectionLabel('#v x5'), index: 1, }; - const rendered = renderSectionLabel(line, { expandSectionRepeats: false }); + const rendered = renderSectionLabel(line, { + expandSectionRepeats: false, + }); const element = htmlToElement(rendered); expect(element).toBeInstanceOf(Node); diff --git a/tests/unit/renderer/components/renderSong.spec.js b/tests/unit/renderer/components/renderSong.spec.js index b2f79288..76550195 100644 --- a/tests/unit/renderer/components/renderSong.spec.js +++ b/tests/unit/renderer/components/renderSong.spec.js @@ -12,8 +12,7 @@ describe('renderSong', () => { }); test('Should return valid html', () => { - const song = -`A B + const song = `A B verseLine1 C.. D.. E verseLine2`; @@ -26,11 +25,9 @@ verseLine2`; }); }); - describe('autoRepeatChords', () => { test('Should render auto repeated chords & other lines if autoRepeatChords === true', () => { - const input = -`#v + const input = `#v C G line1-1 A D @@ -39,8 +36,7 @@ line1-2 #v line2-1 line2-2`; - const expected = -`Verse 1 + const expected = `Verse 1 |C |G | line1-1 |A |D | @@ -57,8 +53,7 @@ line2-2`; }); test('Should NOT render auto repeated chords & other lines if autoRepeatChords === false', () => { - const input = -`#v + const input = `#v C G line1-1 A D @@ -67,8 +62,7 @@ line1-2 #v line2-1 line2-2`; - const expected = -`Verse 1 + const expected = `Verse 1 |C |G | line1-1 |A |D | @@ -83,17 +77,14 @@ line2-2`; }); }); - describe('expandSectionRepeat', () => { test('Should repeat section when expandSectionRepeat === true', () => { - const input = -`#v x2 + const input = `#v x2 A B verseLine1 C.. D.. E verseLine2`; - const expected = -`Verse 1 + const expected = `Verse 1 |A |B | verseLine1 |C D |E | @@ -109,14 +100,12 @@ verseLine2`; }); test('Should not repeat section when expandSectionRepeat === false, and display repeat string (x3) after label', () => { - const input = -`#v x2 + const input = `#v x2 A B verseLine1 C.. D.. E verseLine2`; - const expected = -`Verse 1 x2 + const expected = `Verse 1 x2 |A |B | verseLine1 |C D |E | @@ -127,12 +116,10 @@ verseLine2`; }); test('Should number repeats incrementally when expandSectionRepeat === true', () => { - const input = -`#v + const input = `#v #v x2 #v`; - const expected = -`Verse 1 + const expected = `Verse 1 Verse 2 Verse 3 Verse 4`; @@ -142,12 +129,10 @@ Verse 4`; }); test('Should number repeats incrementally when expandSectionRepeat === false', () => { - const input = -`#v + const input = `#v #v x2 #v`; - const expected = -`Verse 1 + const expected = `Verse 1 Verse 2 x2 Verse 3`; const rendered = renderSongText(input, { expandSectionRepeats: false }); @@ -156,11 +141,9 @@ Verse 3`; }); }); - describe('sectionsStats', () => { test('Should number section only if it is repeated', () => { - const input = -`#i + const input = `#i #v #c #v @@ -169,8 +152,7 @@ describe('sectionsStats', () => { #b #c x2 #o`; - const expected = -`Intro + const expected = `Intro Verse 1 Chorus 1 Verse 2 @@ -186,4 +168,3 @@ Outro`; expect(element.textContent).toBe(expected); }); }); - diff --git a/tests/unit/renderer/helpers/getChordSymbol.spec.js b/tests/unit/renderer/helpers/getChordSymbol.spec.js index cb948547..ded713fc 100644 --- a/tests/unit/renderer/helpers/getChordSymbol.spec.js +++ b/tests/unit/renderer/helpers/getChordSymbol.spec.js @@ -8,11 +8,9 @@ describe('getChordSymbol', () => { }); describe.each([ - ['AM7', parseChord('AMaj7'), 'Ama7'], - ['A+', parseChord('Aaug'), 'A+'], + ['A+', parseChord('Aaug'), 'A+'], ['NC', 'NC', 'NC'], - ])('getChordSymbol() for %s', (title, input, output) => { test('returns ' + output, () => { expect(getChordSymbol(input)).toEqual(output); diff --git a/tests/unit/renderer/helpers/getMainAccidental.spec.js b/tests/unit/renderer/helpers/getMainAccidental.spec.js index a4794904..d5348a95 100644 --- a/tests/unit/renderer/helpers/getMainAccidental.spec.js +++ b/tests/unit/renderer/helpers/getMainAccidental.spec.js @@ -8,18 +8,20 @@ describe('getMainAccidental', () => { }); describe.each([ + ['no accidentals', 'A B C', 'sharp'], - ['no accidentals', 'A B C', 'sharp'], - - ['all flats', 'Ab Bb Db Gb', 'flat'], - ['3 flats, 1 sharp', 'Ab Bb Db G#', 'flat'], - ['2 flats, 2 sharps', 'Ab Bb D# G#', 'sharp'], - ['1 flat, 3 sharp', 'Ab F# D# G#', 'sharp'], - ['all sharps', 'A# F# D# G#', 'sharp'], + ['all flats', 'Ab Bb Db Gb', 'flat'], + ['3 flats, 1 sharp', 'Ab Bb Db G#', 'flat'], + ['2 flats, 2 sharps', 'Ab Bb D# G#', 'sharp'], + ['1 flat, 3 sharp', 'Ab F# D# G#', 'sharp'], + ['all sharps', 'A# F# D# G#', 'sharp'], ['NC symbol does not count', 'A# Eb NC', 'sharp'], - ['number of chord occurrences have priority over number of distinct chords', 'Ab Ab Ab G# C#', 'flat'], - + [ + 'number of chord occurrences have priority over number of distinct chords', + 'Ab Ab Ab G# C#', + 'flat', + ], ])('Detect accidentals for: %s', (title, input, output) => { test(input + ' => ' + output, () => { const parsed = parseSong(input); diff --git a/tests/unit/renderer/helpers/getSectionsStats.spec.js b/tests/unit/renderer/helpers/getSectionsStats.spec.js index 5193bd0c..c241a01b 100644 --- a/tests/unit/renderer/helpers/getSectionsStats.spec.js +++ b/tests/unit/renderer/helpers/getSectionsStats.spec.js @@ -17,7 +17,7 @@ describe('getSectionsStats()', () => { '#c x2', '#solo', '#c x2', - '#o' + '#o', ]; const parsed = parseSong(input); const expected = { diff --git a/tests/unit/renderer/spacers/chord/aligned.spec.js b/tests/unit/renderer/spacers/chord/aligned.spec.js index 3a0214c9..43736565 100644 --- a/tests/unit/renderer/spacers/chord/aligned.spec.js +++ b/tests/unit/renderer/spacers/chord/aligned.spec.js @@ -13,86 +13,88 @@ const defaultSpacesAfter = 2; const emptyBeatSpaces = 1; describe.each([ - [ 'fills second, third and fourth beat', 'A', + [{ 1: 1, 2: 0, 3: 0, 4: 0 }], + [0], [ - { 1: 1, 2: 0, 3: 0, 4: 0 } + defaultSpacesAfter + + emptyBeatSpaces + + emptyBeatSpaces + + emptyBeatSpaces, ], - [ 0 ], - [ defaultSpacesAfter + emptyBeatSpaces + emptyBeatSpaces + emptyBeatSpaces ] ], [ 'fills second and fourth beat', 'A.. D7..', + [{ 1: 1, 2: 0, 3: 2, 4: 0 }], + [0, 0, 0], [ - { 1: 1, 2: 0, 3: 2, 4: 0 } + defaultSpacesAfter + emptyBeatSpaces, + defaultSpacesAfter + emptyBeatSpaces, ], - [ 0, 0, 0 ], - [ defaultSpacesAfter + emptyBeatSpaces, defaultSpacesAfter + emptyBeatSpaces ] ], [ 'fills second beat', 'A.. D7. E7.', - [ - { 1: 1, 2: 0, 3: 2, 4: 2 } - ], - [ 0, 0, 0 ], - [ defaultSpacesAfter + emptyBeatSpaces, defaultSpacesAfter, 0 ] + [{ 1: 1, 2: 0, 3: 2, 4: 2 }], + [0, 0, 0], + [defaultSpacesAfter + emptyBeatSpaces, defaultSpacesAfter, 0], ], [ 'fills third beat', 'A. Dmi7.. E7.', - [ - { 1: 1, 2: 4, 3: 0, 4: 2 } - ], - [ 0, 0, 0 ], - [ defaultSpacesAfter, defaultSpacesAfter + emptyBeatSpaces, 0 ] + [{ 1: 1, 2: 4, 3: 0, 4: 2 }], + [0, 0, 0], + [defaultSpacesAfter, defaultSpacesAfter + emptyBeatSpaces, 0], ], [ 'fills space within for "full" beats', 'A. Dmi7.. E7.', - [ - { 1: 3, 2: 6, 3: 0, 4: 3 } - ], - [ 2, 2, 1 ], - [ defaultSpacesAfter, defaultSpacesAfter + emptyBeatSpaces, 0 ] + [{ 1: 3, 2: 6, 3: 0, 4: 3 }], + [2, 2, 1], + [defaultSpacesAfter, defaultSpacesAfter + emptyBeatSpaces, 0], ], [ 'fills space within for "empty" beats', 'A.. D7..', - [ - { 1: 3, 2: 6, 3: 3, 4: 7 } - ], - [ 2, 1 ], - [ defaultSpacesAfter + 6 + defaultSpacesAfter, defaultSpacesAfter + 7 ] + [{ 1: 3, 2: 6, 3: 3, 4: 7 }], + [2, 1], + [defaultSpacesAfter + 6 + defaultSpacesAfter, defaultSpacesAfter + 7], ], +])( + 'Aligned spacer: %s', + (title, chordLine, maxBeatWidth, spacesWithin, spacesAfter) => { + test('Correctly fills .spacesWithin and .spacesAfter properties', () => { + let parsed = parseChordLine(chordLine); + parsed = forEachChordInChordLine( + parsed, + (chord) => (chord.symbol = getChordSymbol(chord.model)) + ); -])('Aligned spacer: %s', (title, chordLine, maxBeatWidth, spacesWithin, spacesAfter) => { - test('Correctly fills .spacesWithin and .spacesAfter properties', () => { - let parsed = parseChordLine(chordLine); - parsed = forEachChordInChordLine(parsed, chord => chord.symbol = getChordSymbol(chord.model)); + const spaced = alignedSpacer(parsed, maxBeatWidth); - const spaced = alignedSpacer(parsed, maxBeatWidth); + let chordIndex = 0; - let chordIndex = 0; + spaced.allBars.forEach((bar) => { + bar.allChords.forEach((chord) => { + expect(chord).toHaveProperty('spacesWithin'); + expect(chord.spacesWithin).toEqual( + spacesWithin[chordIndex] + ); - spaced.allBars.forEach(bar => { - bar.allChords.forEach(chord => { - expect(chord).toHaveProperty('spacesWithin'); - expect(chord.spacesWithin).toEqual(spacesWithin[chordIndex]); + expect(chord).toHaveProperty('spacesAfter'); + expect(chord.spacesAfter).toEqual(spacesAfter[chordIndex]); - expect(chord).toHaveProperty('spacesAfter'); - expect(chord.spacesAfter).toEqual(spacesAfter[chordIndex]); - - chordIndex++; + chordIndex++; + }); }); }); - }); -}); + } +); diff --git a/tests/unit/renderer/spacers/chord/getMaxBeatsWidth.spec.js b/tests/unit/renderer/spacers/chord/getMaxBeatsWidth.spec.js index e53f25d1..7c647a6e 100644 --- a/tests/unit/renderer/spacers/chord/getMaxBeatsWidth.spec.js +++ b/tests/unit/renderer/spacers/chord/getMaxBeatsWidth.spec.js @@ -12,107 +12,58 @@ describe('getMaxBeatsWidth', () => { }); describe.each([ - [ '2 lines / 1 bar / 1 chord, same width ', - [ - 'A', - 'B' - ], - [ - { 1: 1, 2: 0, 3: 0, 4: 0 }, - ] + ['A', 'B'], + [{ 1: 1, 2: 0, 3: 0, 4: 0 }], ], [ '2 lines / 1 bar / 1 chord, different width', - [ - 'A', - 'Bmi7' - ], - [ - { 1: 4, 2: 0, 3: 0, 4: 0 }, - ] + ['A', 'Bmi7'], + [{ 1: 4, 2: 0, 3: 0, 4: 0 }], ], [ '2 lines / 1 bar / 4 chords per bar', - [ - 'A. Bmi7. C. Dmi7.', - 'E7. A7. G7. D7/G.' - ], - [ - { 1: 2, 2: 4, 3: 2, 4: 4 }, - ] + ['A. Bmi7. C. Dmi7.', 'E7. A7. G7. D7/G.'], + [{ 1: 2, 2: 4, 3: 2, 4: 4 }], ], [ '2 lines / 1 bar / 3 chords / gap on beat 2 and 4', - [ - 'A.. Bmi7..', - 'E7' - ], - [ - { 1: 2, 2: 0, 3: 4, 4: 0 }, - ] + ['A.. Bmi7..', 'E7'], + [{ 1: 2, 2: 0, 3: 4, 4: 0 }], ], [ '2 lines / 1 bar / 3 chords / gap on beat 2', - [ - 'A.. Bmi7..', - 'E7... A7.' - ], - [ - { 1: 2, 2: 0, 3: 4, 4: 2 }, - ] + ['A.. Bmi7..', 'E7... A7.'], + [{ 1: 2, 2: 0, 3: 4, 4: 2 }], ], [ '2 lines / 1 bar / 3 chords / gap on beat 3', - [ - 'A. Bmi7...', - 'E7... A7.' - ], - [ - { 1: 2, 2: 4, 3: 0, 4: 2 }, - ] + ['A. Bmi7...', 'E7... A7.'], + [{ 1: 2, 2: 4, 3: 0, 4: 2 }], ], [ '2 lines / 1 bar / 3 chords / gap on beat 4', - [ - 'A. Bmi7...', - 'E7.. A7..' - ], - [ - { 1: 2, 2: 4, 3: 2, 4: 0 }, - ] + ['A. Bmi7...', 'E7.. A7..'], + [{ 1: 2, 2: 4, 3: 2, 4: 0 }], ], [ '3 lines / 1 bar / 4 chords ', - [ - 'A. Bmi7. C. Dmi7.', - 'E7. A7. G7. D7.', - 'E7/G. A. G. D(b5).' - ], - [ - { 1: 4, 2: 4, 3: 2, 4: 5 }, - ] + ['A. Bmi7. C. Dmi7.', 'E7. A7. G7. D7.', 'E7/G. A. G. D(b5).'], + [{ 1: 4, 2: 4, 3: 2, 4: 5 }], ], [ '3 lines / 1 bar / different beat per line ', - [ - 'A', - 'E7. A7...', - 'D7.. A..', - 'Dmi7... C(b5).', - ], - [ - { 1: 4, 2: 2, 3: 1, 4: 5 }, - ] + ['A', 'E7. A7...', 'D7.. A..', 'Dmi7... C(b5).'], + [{ 1: 4, 2: 2, 3: 1, 4: 5 }], ], [ @@ -124,34 +75,32 @@ describe.each([ 'Dmi7... C(b5). G(b5). Dmi7... B7', ], [ - { 1: 4, 2: 2, 3: 1, 4: 5 }, - { 1: 5, 2: 4, 3: 5, 4: 2 }, - { 1: 8, 2: 0, 3: 5, 4: 0 }, - ] + { 1: 4, 2: 2, 3: 1, 4: 5 }, + { 1: 5, 2: 4, 3: 5, 4: 2 }, + { 1: 8, 2: 0, 3: 5, 4: 0 }, + ], ], [ '3 lines / uneven bars per line', + ['A A B C G.. E7..', 'G7', 'G Bmi7.. Ami7.. E7'], [ - 'A A B C G.. E7..', - 'G7', - 'G Bmi7.. Ami7.. E7', + { 1: 2, 2: 0, 3: 0, 4: 0 }, + { 1: 4, 2: 0, 3: 4, 4: 0 }, + { 1: 2, 2: 0, 3: 0, 4: 0 }, + { 1: 1, 2: 0, 3: 0, 4: 0 }, + { 1: 1, 2: 0, 3: 2, 4: 0 }, ], - [ - { 1: 2, 2: 0, 3: 0, 4: 0 }, - { 1: 4, 2: 0, 3: 4, 4: 0 }, - { 1: 2, 2: 0, 3: 0, 4: 0 }, - { 1: 1, 2: 0, 3: 0, 4: 0 }, - { 1: 1, 2: 0, 3: 2, 4: 0 }, - ] ], - ])('getMaxBeatsWidth(): %s', (title, input, output) => { test('Correctly computes the maximum width for each beat', () => { const parsedSong = parseSong(input); let { allLines } = parsedSong; - allLines = forEachChordInSong(allLines, chord => chord.symbol = getChordSymbol(chord.model)); + allLines = forEachChordInSong( + allLines, + (chord) => (chord.symbol = getChordSymbol(chord.model)) + ); const maxBeatsWidth = getMaxBeatsWidth(allLines); expect(maxBeatsWidth).toEqual(output); diff --git a/tests/unit/renderer/spacers/chord/simple.spec.js b/tests/unit/renderer/spacers/chord/simple.spec.js index 52a1acc7..11b083da 100644 --- a/tests/unit/renderer/spacers/chord/simple.spec.js +++ b/tests/unit/renderer/spacers/chord/simple.spec.js @@ -14,29 +14,27 @@ const ts6_8 = parseTimeSignature('6/8'); const ts5_4 = parseTimeSignature('5/4'); describe.each([ - - ['1 bar / 1 chord / 6/8', ts6_8, 'C', [3] ], - ['1 bar / 2 chords / 6/8', ts6_8, 'C. F.', [3, 2] ], - - ['1 bar / 1 chord / 3/4', ts3_4, 'C', [3] ], - ['1 bar / 2 chords (1/2) / 3/4', ts3_4, 'C. F..', [2, 4] ], - ['1 bar / 2 chords (2/1) / 3/4', ts3_4, 'C.. F.', [6, 0] ], - ['1 bar / 3 chords / 3/4', ts3_4, 'C. F. G.', [2, 2, 0] ], - - ['1 bar / 1 chord / 4/4', ts4_4, 'C', [3] ], - ['1 bar / 2 chords (1/3) / 4/4', ts4_4, 'C. F...', [1, 4] ], - ['1 bar / 2 chords (2/2) / 4/4', ts4_4, 'C.. F..', [3, 2] ], - ['1 bar / 2 chords (3/1) / 4/4', ts4_4, 'C... F.', [5, 0] ], - ['1 bar / 3 chords (1/1/2) / 4/4', ts4_4, 'C. F. G..', [1, 1, 3] ], - ['1 bar / 3 chords (1/2/1) / 4/4', ts4_4, 'C. F.. G.', [1, 4, 0] ], - ['1 bar / 3 chords (2/1/1) / 4/4', ts4_4, 'C.. F. G.', [4, 1, 0] ], - ['1 bar / 4 chords / 4/4', ts4_4, 'C. F. G. Em.', [2, 2, 2, 0] ], - - ['1 bar / 1 chords / 5/4', ts5_4, 'C', [2] ], - ['1 bar / 2 chords (1/4) / 5/4', ts5_4, 'C. F....', [2, 2] ], - ['1 bar / 2 chords (2/3) / 5/4', ts5_4, 'C.. F...', [2, 2] ], - ['1 bar / 3 chords (1/2/2) / 5/4', ts5_4, 'C. F.. G..', [2, 2, 2] ], - + ['1 bar / 1 chord / 6/8', ts6_8, 'C', [3]], + ['1 bar / 2 chords / 6/8', ts6_8, 'C. F.', [3, 2]], + + ['1 bar / 1 chord / 3/4', ts3_4, 'C', [3]], + ['1 bar / 2 chords (1/2) / 3/4', ts3_4, 'C. F..', [2, 4]], + ['1 bar / 2 chords (2/1) / 3/4', ts3_4, 'C.. F.', [6, 0]], + ['1 bar / 3 chords / 3/4', ts3_4, 'C. F. G.', [2, 2, 0]], + + ['1 bar / 1 chord / 4/4', ts4_4, 'C', [3]], + ['1 bar / 2 chords (1/3) / 4/4', ts4_4, 'C. F...', [1, 4]], + ['1 bar / 2 chords (2/2) / 4/4', ts4_4, 'C.. F..', [3, 2]], + ['1 bar / 2 chords (3/1) / 4/4', ts4_4, 'C... F.', [5, 0]], + ['1 bar / 3 chords (1/1/2) / 4/4', ts4_4, 'C. F. G..', [1, 1, 3]], + ['1 bar / 3 chords (1/2/1) / 4/4', ts4_4, 'C. F.. G.', [1, 4, 0]], + ['1 bar / 3 chords (2/1/1) / 4/4', ts4_4, 'C.. F. G.', [4, 1, 0]], + ['1 bar / 4 chords / 4/4', ts4_4, 'C. F. G. Em.', [2, 2, 2, 0]], + + ['1 bar / 1 chords / 5/4', ts5_4, 'C', [2]], + ['1 bar / 2 chords (1/4) / 5/4', ts5_4, 'C. F....', [2, 2]], + ['1 bar / 2 chords (2/3) / 5/4', ts5_4, 'C.. F...', [2, 2]], + ['1 bar / 3 chords (1/2/2) / 5/4', ts5_4, 'C. F.. G..', [2, 2, 2]], ])('%s', (title, timeSignature, input, spacesAfter) => { test('Correctly compute .spacesAfter', () => { const parsed = parseChordLine(input, { timeSignature }); @@ -44,8 +42,8 @@ describe.each([ let chordIndex = 0; - spaced.allBars.forEach(bar => { - bar.allChords.forEach(chord => { + spaced.allBars.forEach((bar) => { + bar.allChords.forEach((chord) => { expect(chord).toHaveProperty('spacesAfter'); expect(chord.spacesAfter).toEqual(spacesAfter[chordIndex]); chordIndex++; diff --git a/todo.md b/todo.md index 16fb2a66..790fe9f4 100644 --- a/todo.md +++ b/todo.md @@ -1,5 +1,7 @@ # Tech debt -- memoize most parsing functions + +- memoize most parsing functions # Todo -- remove duplicate chords after simplification + +- remove duplicate chords after simplification diff --git a/webpack.config.js b/webpack.config.js index 34d6d971..95d91766 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,8 +3,9 @@ const path = require('path'); const webpack = require('webpack'); -const { CleanWebpackPlugin }= require('clean-webpack-plugin'); -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; +const { CleanWebpackPlugin } = require('clean-webpack-plugin'); +const BundleAnalyzerPlugin = + require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const buildDir = 'lib'; @@ -23,7 +24,7 @@ const config = { library: 'chord-mark', libraryTarget: 'umd', // https://github.com/webpack/webpack/pull/8625 - globalObject: 'typeof self !== \'undefined\' ? self : this', + globalObject: "typeof self !== 'undefined' ? self : this", }, optimization: { @@ -31,7 +32,7 @@ const config = { }, performance: { - hints: false + hints: false, }, plugins: [ @@ -48,15 +49,14 @@ const config = { { test: /\.js$/, exclude: /node_modules/, - loader: 'babel-loader' + loader: 'babel-loader', }, { test: /\.hbs$/, - loader: 'handlebars-loader' + loader: 'handlebars-loader', }, - ] - } + ], + }, }; module.exports = config; - From 6c51cb7dfa283b311c4322cbf68687b0ed194259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christophe=20No=C3=ABl?= Date: Thu, 22 Jul 2021 18:37:19 +0200 Subject: [PATCH 02/11] parse chord position markers in lyric line --- src/parser/parseLyricLine.js | 32 +++ src/parser/parseTextLine.js | 6 - src/parser/songLinesFactory.js | 2 + src/parser/syntax.js | 1 + tests/integration/parser/parseSong.spec.js | 55 ++++- tests/unit/parser/parseLyricLine.spec.js | 102 ++++++++ tests/unit/parser/songLinesFactory.spec.js | 259 +++++++++++++++------ 7 files changed, 370 insertions(+), 87 deletions(-) create mode 100644 src/parser/parseLyricLine.js delete mode 100644 src/parser/parseTextLine.js create mode 100644 tests/unit/parser/parseLyricLine.spec.js diff --git a/src/parser/parseLyricLine.js b/src/parser/parseLyricLine.js new file mode 100644 index 00000000..8dc871de --- /dev/null +++ b/src/parser/parseLyricLine.js @@ -0,0 +1,32 @@ +import syntax from './syntax'; + +/** + * @typedef {Object} LyricLine + * @type {Object} + * @property {String} lyrics + * @property {Number[]} chordPositions + */ + +/** + * @param {String} string + * @returns {LyricLine} + */ +export default function parseLyricLine(string) { + const regexp = new RegExp(syntax.chordPositionMarker, 'g'); + const stringWithoutPositionMarkers = string.replace(regexp, ''); + + const chordPositions = []; + let tmpString = string; + let position; + + while ((position = tmpString.indexOf(syntax.chordPositionMarker)) !== -1) { + if (!chordPositions.includes(position)) { + chordPositions.push(position); + } + tmpString = tmpString.replace(syntax.chordPositionMarker, ''); + } + return { + lyrics: stringWithoutPositionMarkers, + chordPositions, + }; +} diff --git a/src/parser/parseTextLine.js b/src/parser/parseTextLine.js deleted file mode 100644 index 74fe9626..00000000 --- a/src/parser/parseTextLine.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @typedef {Object} TextLine - * @type {Object} - * @property {Number} chordCount - number of chords in the line - * @property {Bar[]} allBars - */ diff --git a/src/parser/songLinesFactory.js b/src/parser/songLinesFactory.js index 9e1d9735..1de8ce21 100644 --- a/src/parser/songLinesFactory.js +++ b/src/parser/songLinesFactory.js @@ -11,6 +11,7 @@ import isEmptyLine from './matchers/isEmptyLine'; import parseTimeSignature from './parseTimeSignature'; import parseChordLine from './parseChordLine'; import parseSectionLabel from './parseSectionLabel'; +import parseLyricLine from './parseLyricLine'; import { getNthOfLabel } from './helper/songs'; @@ -165,6 +166,7 @@ export default function songLinesFactory() { return { string, type: lineTypes.TEXT, + model: parseLyricLine(string), }; } diff --git a/src/parser/syntax.js b/src/parser/syntax.js index 2c8a2e48..bd62478e 100644 --- a/src/parser/syntax.js +++ b/src/parser/syntax.js @@ -5,4 +5,5 @@ export default { noChord: 'NC', noLyrics: 'NL', sectionLabel: '#', + chordPositionMarker: '_', }; diff --git a/tests/integration/parser/parseSong.spec.js b/tests/integration/parser/parseSong.spec.js index 1dd92c3f..a45d0d1a 100644 --- a/tests/integration/parser/parseSong.spec.js +++ b/tests/integration/parser/parseSong.spec.js @@ -2,6 +2,7 @@ import parseChordLine from '../../../src/parser/parseChordLine'; import parseChord from '../../../src/parser/parseChord'; import parseSong from '../../../src/parser/parseSong'; import parseTimeSignature from '../../../src/parser/parseTimeSignature'; +import parseLyricLine from '../../../src/parser/parseLyricLine'; describe('parseSong', () => { test('', () => { @@ -42,50 +43,81 @@ Let it be`; { type: 'text', string: 'When I find myself in times of trouble', + model: parseLyricLine( + 'When I find myself in times of trouble' + ), }, { type: 'chord', string: 'Am.. F..', model: parseChordLine('Am.. F..'), }, - { type: 'text', string: 'Mother mary comes to me' }, + { + type: 'text', + string: 'Mother mary comes to me', + model: parseLyricLine('Mother mary comes to me'), + }, { type: 'chord', string: 'C.. G..', model: parseChordLine('C.. G..'), }, - { type: 'text', string: 'Speaking words of wisdom' }, + { + type: 'text', + string: 'Speaking words of wisdom', + model: parseLyricLine('Speaking words of wisdom'), + }, { type: 'chord', string: 'F. Em. Dm. C.', model: parseChordLine('F. Em. Dm. C.'), }, - { type: 'text', string: 'Let it be' }, + { + type: 'text', + string: 'Let it be', + model: parseLyricLine('Let it be'), + }, { type: 'emptyLine', string: '' }, { type: 'chord', string: 'Am.. G..', model: parseChordLine('Am.. G..'), }, - { type: 'text', string: 'Let it be, let it be' }, + { + type: 'text', + string: 'Let it be, let it be', + model: parseLyricLine('Let it be, let it be'), + }, { type: 'chord', string: 'C.. F..', model: parseChordLine('C.. F..'), }, - { type: 'text', string: 'Let it be, let it be' }, + { + type: 'text', + string: 'Let it be, let it be', + model: parseLyricLine('Let it be, let it be'), + }, { type: 'chord', string: 'C.. G..', model: parseChordLine('C.. G..'), }, - { type: 'text', string: 'Whispers words of wisdom' }, + { + type: 'text', + string: 'Whispers words of wisdom', + model: parseLyricLine('Whispers words of wisdom'), + }, { type: 'chord', string: 'F. Em. Dm. C.', model: parseChordLine('F. Em. Dm. C.'), }, - { type: 'text', string: 'Let it be' }, + { + type: 'text', + string: 'Let it be', + model: parseLyricLine('Let it be'), + }, ], allChords: [ { model: parseChord('C'), occurrences: 6 }, @@ -117,13 +149,20 @@ Let it be`; { type: 'text', string: 'When I find myself in times of trouble', + model: parseLyricLine( + 'When I find myself in times of trouble' + ), }, { type: 'chord', string: 'Am.. F..', model: parseChordLine('Am.. F..'), }, - { type: 'text', string: 'Mother mary comes to me' }, + { + type: 'text', + string: 'Mother mary comes to me', + model: parseLyricLine('Mother mary comes to me'), + }, ], allChords: [ { model: parseChord('C'), occurrences: 1 }, diff --git a/tests/unit/parser/parseLyricLine.spec.js b/tests/unit/parser/parseLyricLine.spec.js new file mode 100644 index 00000000..4028e78f --- /dev/null +++ b/tests/unit/parser/parseLyricLine.spec.js @@ -0,0 +1,102 @@ +import parseLyricLine from '../../../src/parser/parseLyricLine'; + +describe('parseLyricLine', () => { + test('Module', () => { + expect(parseLyricLine).toBeInstanceOf(Function); + }); +}); + +describe.each([ + ['empty line', '', '', []], + [ + 'no chords position marker', + 'A lyric line with no chords', + 'A lyric line with no chords', + [], + ], + [ + '1 chords position marker, beginning', + '_A lyric line with one chord', + 'A lyric line with one chord', + [0], + ], + [ + '1 chords position marker, before lyrics', + '_ A lyric line with one chord', + ' A lyric line with one chord', + [0], + ], + [ + '1 chords position marker, middle', + 'A lyric line _with one chord', + 'A lyric line with one chord', + ['A lyric line '.length], + ], + [ + '1 chords position marker, end', + 'A lyric line with one chord _', + 'A lyric line with one chord ', + ['A lyric line with one chord '.length], + ], + [ + 'multiple chords position marker', + '_A lyric _line with _multiple _chords', + 'A lyric line with multiple chords', + [ + 0, + 'A lyric '.length, + 'A lyric line with '.length, + 'A lyric line with multiple '.length, + ], + ], + [ + 'multiple position marker together will count as 1', + '_A lyric ___line with a lot __of _chords', + 'A lyric line with a lot of chords', + [ + 0, + 'A lyric '.length, + 'A lyric line with a lot '.length, + 'A lyric line with a lot of '.length, + ], + ], + [ + 'multiple position marker separated with a space', + '_A lyric _ _ _line with a lot _ _of _chords', + 'A lyric line with a lot of chords', + [ + 0, + 'A lyric '.length, + 'A lyric '.length, + 'A lyric '.length, + 'A lyric line with a lot '.length, + 'A lyric line with a lot '.length, + 'A lyric line with a lot of '.length, + ], + ], + [ + 'ending a line with multiple chords without lyrics', + '_A lyric line _ending with a lot of chords _ _ _ _', + 'A lyric line ending with a lot of chords ', + [ + 0, + 'A lyric line '.length, + 'A lyric line ending with a lot of chords '.length, + 'A lyric line ending with a lot of chords '.length, + 'A lyric line ending with a lot of chords '.length, + 'A lyric line ending with a lot of chords '.length, + ], + ], +])('%s ', (title, lyricLine, lyrics, chordPositions) => { + test('Correctly detects chords position', () => { + const parsed = parseLyricLine(lyricLine); + expect(parsed).toHaveProperty('chordPositions'); + expect(parsed.chordPositions).toEqual(chordPositions); + }); + + test('Correctly remove chord placeholders in lyrics', () => { + const parsed = parseLyricLine(lyricLine); + expect(parsed).toHaveProperty('lyrics'); + expect(parsed.lyrics).toEqual(lyrics); + }); +}); diff --git a/tests/unit/parser/songLinesFactory.spec.js b/tests/unit/parser/songLinesFactory.spec.js index ea636276..d3946408 100644 --- a/tests/unit/parser/songLinesFactory.spec.js +++ b/tests/unit/parser/songLinesFactory.spec.js @@ -1,4 +1,5 @@ jest.mock('../../../src/parser/parseChordLine'); +jest.mock('../../../src/parser/parseLyricLine'); import _ from 'lodash'; @@ -7,6 +8,7 @@ import songLinesFactory from '../../../src/parser/songLinesFactory'; import parseChordLine from '../../../src/parser/parseChordLine'; import parseSectionLabel from '../../../src/parser/parseSectionLabel'; import parseTimeSignature from '../../../src/parser/parseTimeSignature'; +import parseLyricLine from '../../../src/parser/parseLyricLine'; describe('songLinesFactory', () => { test('Module', () => { @@ -23,11 +25,13 @@ describe('songLinesFactory', () => { beforeEach(() => { parseChordLine.mockClear(); + parseLyricLine.mockClear(); }); describe('ChordLines', () => { test('Correctly detect and parses chord lines', () => { parseChordLine.mockImplementation((chordLine) => chordLine); + parseLyricLine.mockImplementation((lyricLine) => lyricLine); const input = `C.. G.. When I find myself in times of trouble @@ -49,22 +53,46 @@ Let it be`; const expected = [ { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'When I find myself in times of trouble' }, + { + type: 'text', + model: 'When I find myself in times of trouble', + string: 'When I find myself in times of trouble', + }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'Mother mary comes to me' }, + { + type: 'text', + model: 'Mother mary comes to me', + string: 'Mother mary comes to me', + }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'Speaking words of wisdom' }, + { + type: 'text', + model: 'Speaking words of wisdom', + string: 'Speaking words of wisdom', + }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', string: 'Let it be' }, + { type: 'text', model: 'Let it be', string: 'Let it be' }, { type: 'emptyLine', string: '' }, { type: 'chord', string: 'Am.. G..', model: 'Am.. G..' }, - { type: 'text', string: 'Let it be, let it be' }, + { + type: 'text', + model: 'Let it be, let it be', + string: 'Let it be, let it be', + }, { type: 'chord', string: 'C.. F..', model: 'C.. F..' }, - { type: 'text', string: 'Let it be, let it be' }, + { + type: 'text', + model: 'Let it be, let it be', + string: 'Let it be, let it be', + }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'Whispers words of wisdom' }, + { + type: 'text', + model: 'Whispers words of wisdom', + string: 'Whispers words of wisdom', + }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', string: 'Let it be' }, + { type: 'text', model: 'Let it be', string: 'Let it be' }, ]; const songLines = songLinesFactory(); @@ -80,7 +108,7 @@ Let it be`; const input = 'C. D.. E..'; - const expected = [{ type: 'text', string: input }]; + const expected = [{ type: 'text', string: input, model: input }]; const songLines = songLinesFactory(); input.split('\n').forEach(songLines.addLine); @@ -318,14 +346,26 @@ Let it be`; }, { type: 'timeSignature', string: '4/4', model: _.cloneDeep(ts4_4) }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'When I find myself in times of trouble' }, + { + type: 'text', + model: 'When I find myself in times of trouble', + string: 'When I find myself in times of trouble', + }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'Mother mary comes to me' }, + { + type: 'text', + model: 'Mother mary comes to me', + string: 'Mother mary comes to me', + }, { type: 'emptyLine', string: '' }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'Speaking words of wisdom' }, + { + type: 'text', + model: 'Speaking words of wisdom', + string: 'Speaking words of wisdom', + }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', string: 'Let it be' }, + { type: 'text', model: 'Let it be', string: 'Let it be' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -347,14 +387,22 @@ Let it be`; model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'And in my hour of darkness' }, + { + type: 'text', + model: 'And in my hour of darkness', + string: 'And in my hour of darkness', + }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'she is standing right in front of me' }, + { + type: 'text', + model: 'she is standing right in front of me', + string: 'she is standing right in front of me', + }, { type: 'emptyLine', string: '' }, { type: 'chord', @@ -362,14 +410,18 @@ Let it be`; model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'Speaking words of wisdom' }, + { + type: 'text', + model: 'Speaking words of wisdom', + string: 'Speaking words of wisdom', + }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'Let it be' }, + { type: 'text', model: 'Let it be', string: 'Let it be' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -380,13 +432,25 @@ Let it be`; id: 'c1', }, { type: 'chord', string: 'Am.. G..', model: 'Am.. G..' }, - { type: 'text', string: 'Let it be, let it be' }, + { + type: 'text', + model: 'Let it be, let it be', + string: 'Let it be, let it be', + }, { type: 'chord', string: 'C.. F..', model: 'C.. F..' }, - { type: 'text', string: 'Let it be, let it be' }, + { + type: 'text', + model: 'Let it be, let it be', + string: 'Let it be, let it be', + }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'Whispers words of wisdom' }, + { + type: 'text', + model: 'Whispers words of wisdom', + string: 'Whispers words of wisdom', + }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', string: 'Let it be' }, + { type: 'text', model: 'Let it be', string: 'Let it be' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -408,14 +472,22 @@ Let it be`; model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'And when the broken hearted people' }, + { + type: 'text', + model: 'And when the broken hearted people', + string: 'And when the broken hearted people', + }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'Living in the world agree' }, + { + type: 'text', + model: 'Living in the world agree', + string: 'Living in the world agree', + }, { type: 'emptyLine', string: '' }, { type: 'chord', @@ -423,14 +495,18 @@ Let it be`; model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'There will be an answer' }, + { + type: 'text', + model: 'There will be an answer', + string: 'There will be an answer', + }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'Let it be' }, + { type: 'text', model: 'Let it be', string: 'Let it be' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -446,28 +522,40 @@ Let it be`; model: 'Am.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'Let it be, let it be' }, + { + type: 'text', + model: 'Let it be, let it be', + string: 'Let it be, let it be', + }, { type: 'chord', string: 'C.. F..', model: 'C.. F..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'Let it be, let it be' }, + { + type: 'text', + model: 'Let it be, let it be', + string: 'Let it be, let it be', + }, { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'Whispers words of wisdom' }, + { + type: 'text', + model: 'Whispers words of wisdom', + string: 'Whispers words of wisdom', + }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'Let it be' }, + { type: 'text', model: 'Let it be', string: 'Let it be' }, ]; const songLines = songLinesFactory(); @@ -511,13 +599,13 @@ line3-3 id: 'v1', }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1' }, + { type: 'text', string: 'line1-1', model: 'line1-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'line1-2' }, + { type: 'text', string: 'line1-2', model: 'line1-2' }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-3' }, + { type: 'text', string: 'line1-3', model: 'line1-3' }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', string: 'line1-4' }, + { type: 'text', string: 'line1-4', model: 'line1-4' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -533,14 +621,14 @@ line3-3 model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line2-1' }, + { type: 'text', string: 'line2-1', model: 'line2-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line2-2' }, + { type: 'text', string: 'line2-2', model: 'line2-2' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -556,21 +644,21 @@ line3-3 model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line3-1' }, + { type: 'text', string: 'line3-1', model: 'line3-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line3-2' }, + { type: 'text', string: 'line3-2', model: 'line3-2' }, { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line3-3' }, + { type: 'text', string: 'line3-3', model: 'line3-3' }, { type: 'emptyLine', string: '' }, { type: 'emptyLine', string: '' }, { type: 'emptyLine', string: '' }, @@ -609,9 +697,9 @@ line2-4`; id: 'v1', }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1' }, + { type: 'text', string: 'line1-1', model: 'line1-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'line1-2' }, + { type: 'text', string: 'line1-2', model: 'line1-2' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -627,18 +715,18 @@ line2-4`; model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line2-1' }, + { type: 'text', string: 'line2-1', model: 'line2-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line2-2' }, + { type: 'text', string: 'line2-2', model: 'line2-2' }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line2-3' }, + { type: 'text', string: 'line2-3', model: 'line2-3' }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', string: 'line2-4' }, + { type: 'text', string: 'line2-4', model: 'line2-4' }, ]; const songLines = songLinesFactory(); @@ -671,9 +759,9 @@ line2-2`; id: 'v1', }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1' }, + { type: 'text', string: 'line1-1', model: 'line1-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'line1-2' }, + { type: 'text', string: 'line1-2', model: 'line1-2' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -689,9 +777,9 @@ line2-2`; model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line2-1' }, + { type: 'text', string: 'line2-1', model: 'line2-1' }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', string: 'line2-2' }, + { type: 'text', string: 'line2-2', model: 'line2-2' }, ]; const songLines = songLinesFactory(); @@ -716,14 +804,14 @@ line2-1`; const expected = [ { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1' }, + { type: 'text', string: 'line1-1', model: 'line1-1' }, { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromChordLineRepeater: true, }, - { type: 'text', string: 'line1-2' }, + { type: 'text', string: 'line1-2', model: 'line1-2' }, { type: 'emptyLine', string: '' }, { type: 'emptyLine', string: '' }, { @@ -732,7 +820,7 @@ line2-1`; model: 'C.. G..', isFromChordLineRepeater: true, }, - { type: 'text', string: 'line2-1' }, + { type: 'text', string: 'line2-1', model: 'line2-1' }, ]; const songLines = songLinesFactory(); @@ -752,12 +840,12 @@ line1-2 line1-3`; const expected = [ - { type: 'text', string: '/' }, - { type: 'text', string: 'line1-1' }, - { type: 'text', string: '/' }, - { type: 'text', string: 'line1-2' }, - { type: 'text', string: '/' }, - { type: 'text', string: 'line1-3' }, + { type: 'text', string: '/', model: '/' }, + { type: 'text', string: 'line1-1', model: 'line1-1' }, + { type: 'text', string: '/', model: '/' }, + { type: 'text', string: 'line1-2', model: 'line1-2' }, + { type: 'text', string: '/', model: '/' }, + { type: 'text', string: 'line1-3', model: 'line1-3' }, ]; const songLines = songLinesFactory(); @@ -790,9 +878,9 @@ line2-2`; id: 'v1', }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1' }, + { type: 'text', string: 'line1-1', model: 'line1-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'line1-2' }, + { type: 'text', string: 'line1-2', model: 'line1-2' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -808,14 +896,14 @@ line2-2`; model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line2-1' }, + { type: 'text', string: 'line2-1', model: 'line2-1' }, { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromChordLineRepeater: true, }, - { type: 'text', string: 'line2-2' }, + { type: 'text', string: 'line2-2', model: 'line2-2' }, ]; const songLines = songLinesFactory(); @@ -845,9 +933,9 @@ line1-2 id: 'v1', }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1' }, + { type: 'text', string: 'line1-1', model: 'line1-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'line1-2' }, + { type: 'text', string: 'line1-2', model: 'line1-2' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -864,14 +952,24 @@ line1-2 model: 'C.. G..', isFromSectionRepeat: true, }, - { type: 'text', string: 'line1-1', isFromSectionRepeat: true }, + { + type: 'text', + string: 'line1-1', + model: 'line1-1', + isFromSectionRepeat: true, + }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromSectionRepeat: true, }, - { type: 'text', string: 'line1-2', isFromSectionRepeat: true }, + { + type: 'text', + string: 'line1-2', + model: 'line1-2', + isFromSectionRepeat: true, + }, { type: 'emptyLine', string: '', isFromSectionRepeat: true }, ]; @@ -906,9 +1004,9 @@ line2-3 id: 'v1', }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1' }, + { type: 'text', string: 'line1-1', model: 'line1-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'line1-2' }, + { type: 'text', string: 'line1-2', model: 'line1-2' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -924,21 +1022,21 @@ line2-3 model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line2-1' }, + { type: 'text', string: 'line2-1', model: 'line2-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line2-2' }, + { type: 'text', string: 'line2-2', model: 'line2-2' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromChordLineRepeater: true, }, - { type: 'text', string: 'line2-3' }, + { type: 'text', string: 'line2-3', model: 'line2-3' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -956,7 +1054,12 @@ line2-3 isFromSectionRepeat: true, isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line2-1', isFromSectionRepeat: true }, + { + type: 'text', + string: 'line2-1', + model: 'line2-1', + isFromSectionRepeat: true, + }, { type: 'chord', string: 'Am.. F..', @@ -964,7 +1067,12 @@ line2-3 isFromSectionRepeat: true, isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line2-2', isFromSectionRepeat: true }, + { + type: 'text', + string: 'line2-2', + model: 'line2-2', + isFromSectionRepeat: true, + }, { type: 'chord', string: 'Am.. F..', @@ -972,7 +1080,12 @@ line2-3 isFromSectionRepeat: true, isFromChordLineRepeater: true, }, - { type: 'text', string: 'line2-3', isFromSectionRepeat: true }, + { + type: 'text', + string: 'line2-3', + model: 'line2-3', + isFromSectionRepeat: true, + }, { type: 'emptyLine', string: '', isFromSectionRepeat: true }, ]; From 633aa17830232bc9f2a23fa730d9bc4fbce8326d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christophe=20No=C3=ABl?= Date: Fri, 23 Jul 2021 08:29:28 +0200 Subject: [PATCH 03/11] pass chordIndex and barIndex to forEachChordInChordLine handler --- src/parser/helper/songs.js | 6 ++-- tests/unit/parser/helpers/song.spec.js | 38 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/parser/helper/songs.js b/src/parser/helper/songs.js index 0ba0046e..f069268d 100644 --- a/src/parser/helper/songs.js +++ b/src/parser/helper/songs.js @@ -30,9 +30,9 @@ export function forEachChordInSong(allLines, fn) { export function forEachChordInChordLine(chordLine, fn) { const newChordLine = _cloneDeep(chordLine); - newChordLine.allBars.forEach((bar) => { - bar.allChords.forEach((chord) => { - fn(chord); + newChordLine.allBars.forEach((bar, barIndex) => { + bar.allChords.forEach((chord, chordIndex) => { + fn(chord, chordIndex, barIndex); }); }); diff --git a/tests/unit/parser/helpers/song.spec.js b/tests/unit/parser/helpers/song.spec.js index ec85c03e..c9dd9416 100644 --- a/tests/unit/parser/helpers/song.spec.js +++ b/tests/unit/parser/helpers/song.spec.js @@ -88,6 +88,44 @@ describe('forEachChordInChordLine', () => { }); }); }); + + test('Should provide the proper barIndex and chordIndex', () => { + expect.assertions(32); + + const chordLine = 'Am... G/B. C E. F. G. B7. Em'; + const parsed = parseChordLine(chordLine); + const applied = forEachChordInChordLine( + parsed, + (chord, chordIndex, barIndex) => { + chord.chordIndex = chordIndex; + chord.barIndex = barIndex; + } + ); + + let i = 0; + const expected = [ + [0, 0], + [0, 1], + + [1, 0], + + [2, 0], + [2, 1], + [2, 2], + [2, 3], + + [3, 0], + ]; + applied.allBars.forEach((bar, barIndex) => { + bar.allChords.forEach((chord, chordIndex) => { + expect(chord.barIndex).toBe(expected[i][0]); + expect(chord.barIndex).toBe(barIndex); + expect(chord.chordIndex).toBe(expected[i][1]); + expect(chord.chordIndex).toBe(chordIndex); + i++; + }); + }); + }); }); describe('getNthOfLabel', () => { From 95a15aaad5b10a1880b685d00e53f38b5f070b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christophe=20No=C3=ABl?= Date: Sat, 28 Aug 2021 19:04:23 +0200 Subject: [PATCH 04/11] add chordLyrics spacer --- src/renderer/components/renderTextLine.js | 2 +- src/renderer/spacers/chord/chordLyrics.js | 68 ++++++++++++ src/renderer/syntax.js | 4 + .../spacers/chord/chordLyrics.spec.js | 105 ++++++++++++++++++ 4 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 src/renderer/spacers/chord/chordLyrics.js create mode 100644 src/renderer/syntax.js create mode 100644 tests/unit/renderer/spacers/chord/chordLyrics.spec.js diff --git a/src/renderer/components/renderTextLine.js b/src/renderer/components/renderTextLine.js index 34f40a12..582ff951 100644 --- a/src/renderer/components/renderTextLine.js +++ b/src/renderer/components/renderTextLine.js @@ -5,5 +5,5 @@ import textLineTpl from './tpl/textLine.hbs'; * @returns {String} rendered html */ export default function render(textLine) { - return textLineTpl({ textLine: textLine.string }); + return textLineTpl({ textLine: textLine.lyrics }); } diff --git a/src/renderer/spacers/chord/chordLyrics.js b/src/renderer/spacers/chord/chordLyrics.js new file mode 100644 index 00000000..4cf866ef --- /dev/null +++ b/src/renderer/spacers/chord/chordLyrics.js @@ -0,0 +1,68 @@ +import _cloneDeep from 'lodash/cloneDeep'; +import { forEachChordInChordLine } from '../../../parser/helper/songs'; + +import syntax from '../../syntax'; //fixme rename + +const chordSpaceAfterDefault = 1; + +/** + * @param {ChordLine} chordLineInput + * @param {LyricLine} lyricsLineInput + */ +export default function space(chordLineInput, lyricsLineInput) { + const chordLine = _cloneDeep(chordLineInput); + const lyricsLine = _cloneDeep(lyricsLineInput); + + const tokenizedLyrics = lyricsLine.chordPositions.map( + (position, i, allPositions) => { + return lyricsLine.lyrics.substring(position, allPositions[i + 1]); + } + ); + + let chordToken; + let lyricToken; + let currentBarIndex = 0; + let spacedLyricsLine = ''; + + const spacedChordLine = forEachChordInChordLine( + chordLine, + (chord, chordIndex, barIndex) => { + lyricToken = tokenizedLyrics.shift(); + + if (lyricToken) { + chordToken = chord.symbol; + if (isFirstChord(barIndex, chordIndex)) { + chordToken = syntax.barSeparator + chordToken; + } else if (isNewBar(currentBarIndex, barIndex)) { + chordToken = syntax.barSeparator + chordToken; + currentBarIndex = barIndex; + } + if (lyricToken.length - chordToken.length > 0) { + chord.spacesAfter = lyricToken.length - chordToken.length; + } else { + chord.spacesAfter = chordSpaceAfterDefault; + lyricToken += syntax.lyricsSpacer.repeat( + chordToken.length - + lyricToken.length + + chordSpaceAfterDefault + ); + } + spacedLyricsLine += lyricToken; + } + } + ); + + if (tokenizedLyrics.length) { + spacedLyricsLine += tokenizedLyrics.join(''); + } + lyricsLine.lyrics = spacedLyricsLine.trim(); + + return { + chordLine: spacedChordLine, + lyricsLine, + }; +} + +const isFirstChord = (barIndex, chordIndex) => + barIndex === 0 && chordIndex === 0; +const isNewBar = (currentBarIndex, barIndex) => currentBarIndex !== barIndex; diff --git a/src/renderer/syntax.js b/src/renderer/syntax.js new file mode 100644 index 00000000..45fba0ac --- /dev/null +++ b/src/renderer/syntax.js @@ -0,0 +1,4 @@ +export default { + lyricsSpacer: ' ', + barSeparator: '|', +}; diff --git a/tests/unit/renderer/spacers/chord/chordLyrics.spec.js b/tests/unit/renderer/spacers/chord/chordLyrics.spec.js new file mode 100644 index 00000000..d72ce075 --- /dev/null +++ b/tests/unit/renderer/spacers/chord/chordLyrics.spec.js @@ -0,0 +1,105 @@ +import stripTags from '../../../../../src/core/dom/stripTags'; +import { forEachChordInChordLine } from '../../../../../src/parser/helper/songs'; + +import chordLyricsSpacer from '../../../../../src/renderer/spacers/chord/chordLyrics'; +import parseLyricLine from '../../../../../src/parser/parseLyricLine'; +import parseChordLine from '../../../../../src/parser/parseChordLine'; +import renderTextLine from '../../../../../src/renderer/components/renderTextLine'; +import renderChordLine from '../../../../../src/renderer/components/renderChordLine'; +import getChordSymbol from '../../../../../src/renderer/helpers/getChordSymbol'; + +describe('chordLyricsSpacer', () => { + test('Module', () => { + expect(chordLyricsSpacer).toBeInstanceOf(Function); + }); +}); + +describe.each([ + [ + 'Single character chords', + 'A D A E', + '_Put me _on top _of the correct _lyrics', + '|A |D |A |E |', + 'Put me on top of the correct lyrics', + ], + [ + 'Long chords names', + 'Am Dm7 A7(b9) E7', + '_Put me _on top _of the correct _lyrics', + '|Ami |Dmi7 |A7(b9) |E7 |', + 'Put me on top of the correct lyrics', + ], + [ + 'Multiple chords per bar', + 'A.. B.. Dm7 A.. Gmi. F7. E7', + '_Put _me _on top _of _the _correct _lyrics', + '|A B |Dmi7 |A Gmi F7 |E7 |', + 'Put me on top of the correct lyrics', + ], + [ + 'Lyrics shorter than chords names should be spaced', + 'A7(b9).. BmiMa7.. Dmi7 A7(b9).. Gmi13. F7(b9,#11). E7', + '_Put _me _on top _of _the _correct _lyrics', + '|A7(b9) BmiMa7 |Dmi7 |A7(b9) Gmi13 F7(b9,#11) |E7 |', + 'Put me on top of the correct lyrics', + ], + [ + 'Chord symbol size equals lyric token size', + 'Ami7 Bmi D13 F13', + '_Put _me _on _top', + '|Ami7 |Bmi |D13 |F13 |', + 'Put me on top', + ], + [ + 'Chord token size (incl separator and space) equals lyric token size', + 'A7 B D F7', + '_Put _me _on _top', + '|A7 |B |D |F7 |', + 'Put me on top', + ], + [ + 'Extra chord position markers should be ignored', + 'A7 B', + '_Put _me _on _top', + '|A7 |B |', + 'Put me on top', + ], + [ + 'Spacing of extra chords should stay untouched (2 by default)', + 'A7 B C7 B7 F7', + '_Put me _on top', + '|A7 |B |C7 |B7 |F7 |', + 'Put me on top', + ], +])( + '%s', + ( + title, + chordLineInput, + LyricsLineInput, + chordsLineOutput, + LyricsLineOutput + ) => { + test('Correctly space chord & lyrics lines', () => { + const parsedLyrics = parseLyricLine(LyricsLineInput); + + const parsedChords = parseChordLine(chordLineInput); + const parsedChordsWithSymbols = forEachChordInChordLine( + parsedChords, + (chord) => { + chord.symbol = getChordSymbol(chord.model); + } + ); + const { chordLine, lyricsLine } = chordLyricsSpacer( + parsedChordsWithSymbols, + parsedLyrics + ); + + const renderedChords = renderChordLine(chordLine); + const renderedLyrics = renderTextLine(lyricsLine); + + expect(stripTags(renderedChords)).toEqual(chordsLineOutput); + expect(stripTags(renderedLyrics)).toEqual(LyricsLineOutput); + }); + } +); From afa99cbcba87b9da1cf9c8d737a6fd8b2cfce36a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christophe=20No=C3=ABl?= Date: Sun, 29 Aug 2021 08:50:11 +0200 Subject: [PATCH 05/11] handle offset in chordline rendering --- src/renderer/components/renderChordLine.js | 7 +++++- src/renderer/components/tpl/chordLine.hbs | 2 +- src/renderer/syntax.js | 1 + .../components/renderChordLine.spec.js | 22 +++++++++++++++++++ 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/renderChordLine.js b/src/renderer/components/renderChordLine.js index c4ca090a..46d74d97 100644 --- a/src/renderer/components/renderChordLine.js +++ b/src/renderer/components/renderChordLine.js @@ -2,6 +2,7 @@ import chordLineTpl from './tpl/chordLine.hbs'; import renderBarContent from './renderBarContent'; import barSeparatorTpl from './tpl/barSeparator.hbs'; +import syntax from '../syntax'; /** * @param {ChordLine} chordLineModel @@ -17,5 +18,9 @@ export default function renderChordLine(chordLineModel) { const chordLine = barSeparator + allBarsRendered.join(barSeparator) + barSeparator; - return chordLineTpl({ chordLine }); + const chordLineOffset = syntax.chordLineOffsetSpacer.repeat( + chordLineModel.offset || 0 + ); + + return chordLineTpl({ chordLineOffset, chordLine }); } diff --git a/src/renderer/components/tpl/chordLine.hbs b/src/renderer/components/tpl/chordLine.hbs index 0c4c6573..3ade09d1 100644 --- a/src/renderer/components/tpl/chordLine.hbs +++ b/src/renderer/components/tpl/chordLine.hbs @@ -1 +1 @@ -{{{ this.chordLine }}} \ No newline at end of file +{{#if this.chordLineOffset}}{{{ this.chordLineOffset }}}{{/if}}{{{ this.chordLine }}} \ No newline at end of file diff --git a/src/renderer/syntax.js b/src/renderer/syntax.js index 45fba0ac..f580c55f 100644 --- a/src/renderer/syntax.js +++ b/src/renderer/syntax.js @@ -1,4 +1,5 @@ export default { lyricsSpacer: ' ', + chordLineOffsetSpacer: ' ', barSeparator: '|', }; diff --git a/tests/unit/renderer/components/renderChordLine.spec.js b/tests/unit/renderer/components/renderChordLine.spec.js index 71d2609c..88f95c0e 100644 --- a/tests/unit/renderer/components/renderChordLine.spec.js +++ b/tests/unit/renderer/components/renderChordLine.spec.js @@ -42,3 +42,25 @@ describe.each([ expect(stripTags(rendered)).toEqual(output); }); }); + +describe.each([ + ['A B C', undefined, '|A|B|C|'], + ['A B C', 0, '|A|B|C|'], + ['A B C', 1, ' |A|B|C|'], + ['A B C', 2, ' |A|B|C|'], + ['A B C', 3, ' |A|B|C|'], + ['A B C', 10, ' |A|B|C|'], +])('respect offset parameter', (input, offset, output) => { + test('offset chordline by ' + (offset || '0'), () => { + renderBarContent.mockImplementation((bar) => + bar.allChords.map((chord) => getChordSymbol(chord.model)).join(' ') + ); + + const chordLine = parseChordLine(input); + chordLine.offset = offset; + + const rendered = renderChordLine(chordLine); + + expect(stripTags(rendered)).toEqual(output); + }); +}); From 5b0d9790bc568a34bfe702f554276e9763738d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christophe=20No=C3=ABl?= Date: Sun, 29 Aug 2021 14:14:53 +0200 Subject: [PATCH 06/11] Fix renderTextLine --- src/renderer/components/renderTextLine.js | 4 ++-- tests/unit/renderer/components/renderTextLine.spec.js | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/renderer/components/renderTextLine.js b/src/renderer/components/renderTextLine.js index 582ff951..580088c0 100644 --- a/src/renderer/components/renderTextLine.js +++ b/src/renderer/components/renderTextLine.js @@ -1,9 +1,9 @@ import textLineTpl from './tpl/textLine.hbs'; /** - * @param {SongTextLine} textLine + * @param {LyricLine} textLine * @returns {String} rendered html */ export default function render(textLine) { - return textLineTpl({ textLine: textLine.lyrics }); + return textLineTpl({ textLine: textLine.model.lyrics }); } diff --git a/tests/unit/renderer/components/renderTextLine.spec.js b/tests/unit/renderer/components/renderTextLine.spec.js index 0ea9e9f3..12e91a4a 100644 --- a/tests/unit/renderer/components/renderTextLine.spec.js +++ b/tests/unit/renderer/components/renderTextLine.spec.js @@ -7,7 +7,13 @@ describe('renderTextLine', () => { }); test('Should return valid html', () => { - const rendered = renderTextLine({ string: 'textContent' }); + const rendered = renderTextLine({ + string: 'textContent', + model: { + lyrics: 'textContent', + chordPositions: [], + }, + }); const element = htmlToElement(rendered); expect(element).toBeInstanceOf(Node); From 1660d24a2e4ef5f90c000ec3097fabe2f2de39b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christophe=20No=C3=ABl?= Date: Sun, 29 Aug 2021 14:15:50 +0200 Subject: [PATCH 07/11] Add alignChordsWithLyrics rendering option --- src/renderer/components/renderSong.js | 25 +++++++-- src/renderer/spacers/chord/chordLyrics.js | 51 ++++++++++++++++--- .../renderer/components/renderSong.spec.js | 28 ++++++++++ .../spacers/chord/chordLyrics.spec.js | 32 +++++++++++- 4 files changed, 124 insertions(+), 12 deletions(-) diff --git a/src/renderer/components/renderSong.js b/src/renderer/components/renderSong.js index e4089616..f993fe87 100644 --- a/src/renderer/components/renderSong.js +++ b/src/renderer/components/renderSong.js @@ -2,10 +2,11 @@ import getMaxBeatsWidth from '../spacers/chord/getMaxBeatsWidth'; import simpleChordSpacer from '../spacers/chord/simple'; import alignedChordSpacer from '../spacers/chord/aligned'; +import chordLyricsSpacer from '../spacers/chord/chordLyrics'; import { forEachChordInSong } from '../../parser/helper/songs'; -import renderChordLine from './renderChordLine'; +import renderChordLine from './renderChordLine'; //fixme should be renderChordLineModel import renderEmptyLine from './renderEmptyLine'; import renderLine from './renderLine'; import renderSectionLabel from './renderSectionLabel'; @@ -24,6 +25,7 @@ import lineTypes from '../../parser/lineTypes'; /** * @param {Song} parsedSong * @param {Boolean} alignBars + * @param {Boolean} alignChordsWithLyrics * @param {Number} transposeValue * @param {('auto'|'flat'|'sharp')} accidentalsType * @param {Boolean} harmonizeAccidentals @@ -37,6 +39,7 @@ export default function renderSong( parsedSong, { alignBars = false, + alignChordsWithLyrics = true, transposeValue = 0, accidentalsType = 'auto', harmonizeAccidentals = true, @@ -72,15 +75,29 @@ export default function renderSong( const song = allLines .filter(shouldRenderLine) - .map((line) => { + .map((line, lineIndex, allFilteredLines) => { let rendered; if (line.type === lineTypes.CHORD) { - // todo: move this in renderChordLine - const spaced = alignBars + // todo: move this in renderChordLine? + let spaced = alignBars ? alignedChordSpacer(line.model, maxBeatsWidth) : simpleChordSpacer(line.model); + const nextLine = allFilteredLines[lineIndex + 1]; + if ( + alignChordsWithLyrics && + nextLine && + nextLine.type === lineTypes.TEXT + ) { + const { chordLine, lyricsLine } = chordLyricsSpacer( + spaced, + nextLine.model + ); + allFilteredLines[lineIndex + 1].model = lyricsLine; + spaced = chordLine; + } + rendered = renderChordLine(spaced); } else if (line.type === lineTypes.EMPTY_LINE) { rendered = renderEmptyLine(); diff --git a/src/renderer/spacers/chord/chordLyrics.js b/src/renderer/spacers/chord/chordLyrics.js index 4cf866ef..ace2d57e 100644 --- a/src/renderer/spacers/chord/chordLyrics.js +++ b/src/renderer/spacers/chord/chordLyrics.js @@ -8,21 +8,29 @@ const chordSpaceAfterDefault = 1; /** * @param {ChordLine} chordLineInput * @param {LyricLine} lyricsLineInput + * @returns {Object} */ export default function space(chordLineInput, lyricsLineInput) { const chordLine = _cloneDeep(chordLineInput); const lyricsLine = _cloneDeep(lyricsLineInput); + if (hasNoPositionMarkers(lyricsLine)) { + return { + chordLine, + lyricsLine, + }; + } + const tokenizedLyrics = lyricsLine.chordPositions.map( (position, i, allPositions) => { return lyricsLine.lyrics.substring(position, allPositions[i + 1]); } ); + let spacedLyricsLine = ''; let chordToken; let lyricToken; let currentBarIndex = 0; - let spacedLyricsLine = ''; const spacedChordLine = forEachChordInChordLine( chordLine, @@ -37,25 +45,40 @@ export default function space(chordLineInput, lyricsLineInput) { chordToken = syntax.barSeparator + chordToken; currentBarIndex = barIndex; } + + if (startsWithSpace(lyricToken)) { + lyricToken = + syntax.lyricsSpacer.repeat(chordToken.length) + + lyricToken; + } + if (lyricToken.length - chordToken.length > 0) { chord.spacesAfter = lyricToken.length - chordToken.length; } else { chord.spacesAfter = chordSpaceAfterDefault; - lyricToken += syntax.lyricsSpacer.repeat( + const lyricsSpaceAfter = chordToken.length - - lyricToken.length + - chordSpaceAfterDefault - ); + lyricToken.length + + chordSpaceAfterDefault; + lyricToken += syntax.lyricsSpacer.repeat(lyricsSpaceAfter); } spacedLyricsLine += lyricToken; + chord.spacesWithin = 0; } } ); + if (shouldOffsetChordLine(lyricsLine)) { + const chordLineOffset = lyricsLine.chordPositions[0]; + spacedChordLine.offset = chordLineOffset; + spacedLyricsLine = + lyricsLine.lyrics.substring(0, chordLineOffset) + spacedLyricsLine; + } + if (tokenizedLyrics.length) { spacedLyricsLine += tokenizedLyrics.join(''); } - lyricsLine.lyrics = spacedLyricsLine.trim(); + lyricsLine.lyrics = trimEnd(spacedLyricsLine); return { chordLine: spacedChordLine, @@ -63,6 +86,22 @@ export default function space(chordLineInput, lyricsLineInput) { }; } +const hasNoPositionMarkers = (lyricsLine) => + lyricsLine.chordPositions.length === 0; + +const shouldOffsetChordLine = (lyricsLine) => lyricsLine.chordPositions[0] > 0; + const isFirstChord = (barIndex, chordIndex) => barIndex === 0 && chordIndex === 0; + const isNewBar = (currentBarIndex, barIndex) => currentBarIndex !== barIndex; + +// source: https://github.com/es-shims/String.prototype.trimEnd/blob/main/implementation.js +const trimEnd = (str) => { + const endWhitespace = + // eslint-disable-next-line max-len,no-control-regex + /[\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF]*$/; + return str.replace(endWhitespace, ''); +}; + +const startsWithSpace = (str) => str.indexOf(' ') === 0; diff --git a/tests/unit/renderer/components/renderSong.spec.js b/tests/unit/renderer/components/renderSong.spec.js index 76550195..89f0690e 100644 --- a/tests/unit/renderer/components/renderSong.spec.js +++ b/tests/unit/renderer/components/renderSong.spec.js @@ -77,6 +77,34 @@ line2-2`; }); }); +describe('alignChordsWithLyrics', () => { + test('Should align chords with lyrics placeholders', () => { + const input = `#v +C... CM7. F +_Imagine there's _no hea_ven`; + const expected = `Verse +|C CM7 |F | +Imagine there's no heaven`; + const rendered = renderSongText(input, { alignChordsWithLyrics: true }); + const element = htmlToElement(rendered); + expect(element.textContent).toBe(expected); + }); + + test('Should ignore placeholders if chords positioning is disabled (default behavior)', () => { + const input = `#v +C... CM7. F +_Imagine there's _no hea_ven`; + const expected = `Verse +|C CM7|F | +Imagine there's no heaven`; + const rendered = renderSongText(input, { + alignChordsWithLyrics: false, + }); + const element = htmlToElement(rendered); + expect(element.textContent).toBe(expected); + }); +}); + describe('expandSectionRepeat', () => { test('Should repeat section when expandSectionRepeat === true', () => { const input = `#v x2 diff --git a/tests/unit/renderer/spacers/chord/chordLyrics.spec.js b/tests/unit/renderer/spacers/chord/chordLyrics.spec.js index d72ce075..8a3038ae 100644 --- a/tests/unit/renderer/spacers/chord/chordLyrics.spec.js +++ b/tests/unit/renderer/spacers/chord/chordLyrics.spec.js @@ -65,12 +65,40 @@ describe.each([ 'Put me on top', ], [ - 'Spacing of extra chords should stay untouched (2 by default)', + 'Spacing of extra chords should be kept (2 by default)', 'A7 B C7 B7 F7', '_Put me _on top', '|A7 |B |C7 |B7 |F7 |', 'Put me on top', ], + [ + 'No position markers: default spacing should be kept (2 by default)', + 'A B C D', + 'I do not know where to put the chords', + '|A |B |C |D |', + 'I do not know where to put the chords', + ], + [ + 'Edge case: no lyrics, single character chord!', + 'A B C D', + '_ _ _ _', + '|A |B |C |D |', + '', + ], + [ + 'A position marker followed by a space should create space for the full chord name', + 'Ami7(#11) B7(b9)', + '_ A _ second chord shortly after the first one', + '|Ami7(#11) |B7(b9) |', + ' A second chord shortly after the first one', + ], + [ + 'offset the chord rendering if the first position marker is > 0', + 'Ami7(#11) B7(b9)', + 'The first chord comes a bit _later, nice _hu?', + ' |Ami7(#11) |B7(b9) |', + 'The first chord comes a bit later, nice hu?', + ], ])( '%s', ( @@ -96,7 +124,7 @@ describe.each([ ); const renderedChords = renderChordLine(chordLine); - const renderedLyrics = renderTextLine(lyricsLine); + const renderedLyrics = renderTextLine({ model: lyricsLine }); expect(stripTags(renderedChords)).toEqual(chordsLineOutput); expect(stripTags(renderedLyrics)).toEqual(LyricsLineOutput); From 7f764c3b5537d0a57cbb03f71763bc7e369757b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christophe=20No=C3=ABl?= Date: Sun, 29 Aug 2021 19:14:17 +0200 Subject: [PATCH 08/11] rename textLine to lyricLine --- src/parser/lineTypes.js | 2 +- src/parser/songLinesFactory.js | 25 +-- src/renderer/components/renderLyricLine.js | 9 ++ src/renderer/components/renderSong.js | 21 +-- src/renderer/components/renderTextLine.js | 9 -- src/renderer/components/tpl/lyricLine.hbs | 1 + src/renderer/components/tpl/textLine.hbs | 1 - tests/integration/parser/parseSong.spec.js | 20 +-- tests/unit/parser/songLinesFactory.spec.js | 150 +++++++++--------- ...xtLine.spec.js => renderLyricLine.spec.js} | 10 +- .../spacers/chord/chordLyrics.spec.js | 4 +- 11 files changed, 127 insertions(+), 125 deletions(-) create mode 100644 src/renderer/components/renderLyricLine.js delete mode 100644 src/renderer/components/renderTextLine.js create mode 100644 src/renderer/components/tpl/lyricLine.hbs delete mode 100644 src/renderer/components/tpl/textLine.hbs rename tests/unit/renderer/components/{renderTextLine.spec.js => renderLyricLine.spec.js} (58%) diff --git a/src/parser/lineTypes.js b/src/parser/lineTypes.js index 425c8cc6..047c72e1 100644 --- a/src/parser/lineTypes.js +++ b/src/parser/lineTypes.js @@ -6,7 +6,7 @@ export default { CHORD: 'chord', CHORD_LINE_REPEATER: 'chordLineRepeater', EMPTY_LINE: 'emptyLine', + LYRIC: 'lyric', SECTION_LABEL: 'sectionLabel', - TEXT: 'text', TIME_SIGNATURE: 'timeSignature', }; diff --git a/src/parser/songLinesFactory.js b/src/parser/songLinesFactory.js index 1de8ce21..df07178c 100644 --- a/src/parser/songLinesFactory.js +++ b/src/parser/songLinesFactory.js @@ -21,7 +21,7 @@ const defaultTimeSignature = '4/4'; * @typedef {Object} SongLine * @type {Object} * @property {String} string - original line in source file - * @property {String} type - chord|text|timeSignature|sectionLabel... + * @property {String} type - chord|lyric|timeSignature|sectionLabel... * @property {Boolean} [isFromSectionRepeat] - line created by a section repeat directive (x3...) * @property {Boolean} [isFromAutoRepeatChords] - line created by auto repeats of chords from a section to another */ @@ -40,8 +40,9 @@ const defaultTimeSignature = '4/4'; */ /** - * @typedef {SongLine} SongTextLine + * @typedef {SongLine} SongLyricLine * @type {Object} + * @property {LyricLine} model */ /** @@ -116,7 +117,7 @@ export default function songLinesFactory() { } /** - * @returns {SongTextLine} + * @returns {SongLyricLine} */ function getEmptyLine(string) { return { @@ -126,7 +127,7 @@ export default function songLinesFactory() { } /** - * @returns {SongChordLine|SongTextLine} + * @returns {SongChordLine|SongLyricLine} */ function getChordLine(string) { let line; @@ -141,13 +142,13 @@ export default function songLinesFactory() { }; previousChordLine = line; } catch (e) { - line = getTextLine(string); + line = getLyricLine(string); } return line; } /** - * @returns {SongChordLine|SongTextLine} + * @returns {SongChordLine|SongLyricLine} */ function getPreviousChordLine(string) { if (previousChordLine) { @@ -156,16 +157,16 @@ export default function songLinesFactory() { isFromChordLineRepeater: true, }; } - return getTextLine(string); + return getLyricLine(string); } /** - * @returns {SongTextLine} + * @returns {SongLyricLine} */ - function getTextLine(string) { + function getLyricLine(string) { return { string, - type: lineTypes.TEXT, + type: lineTypes.LYRIC, model: parseLyricLine(string), }; } @@ -257,7 +258,7 @@ export default function songLinesFactory() { } else if (isEmptyLine(lineSrc)) { line = getEmptyLine(lineSrc); } else { - line = getTextLine(lineSrc); + line = getLyricLine(lineSrc); } repeatLinesFromBlueprint(line); @@ -287,7 +288,7 @@ function isFirstOfLabel(currentLabel, allLines) { function shouldRepeatLineFromBlueprint(blueprintLine, currentLine) { return ( blueprintLine && - blueprintLine.type !== lineTypes.TEXT && + blueprintLine.type !== lineTypes.LYRIC && blueprintLine.type !== lineTypes.EMPTY_LINE && blueprintLine.type !== currentLine.type && currentLine.type !== lineTypes.EMPTY_LINE diff --git a/src/renderer/components/renderLyricLine.js b/src/renderer/components/renderLyricLine.js new file mode 100644 index 00000000..361d9193 --- /dev/null +++ b/src/renderer/components/renderLyricLine.js @@ -0,0 +1,9 @@ +import lyricLineTpl from './tpl/lyricLine.hbs'; + +/** + * @param {SongLyricLine} lyricLine + * @returns {String} rendered html + */ +export default function render(lyricLine) { + return lyricLineTpl({ lyricLine: lyricLine.model.lyrics }); +} diff --git a/src/renderer/components/renderSong.js b/src/renderer/components/renderSong.js index f993fe87..8ed03004 100644 --- a/src/renderer/components/renderSong.js +++ b/src/renderer/components/renderSong.js @@ -6,11 +6,11 @@ import chordLyricsSpacer from '../spacers/chord/chordLyrics'; import { forEachChordInSong } from '../../parser/helper/songs'; -import renderChordLine from './renderChordLine'; //fixme should be renderChordLineModel +import renderChordLineModel from './renderChordLine'; import renderEmptyLine from './renderEmptyLine'; import renderLine from './renderLine'; import renderSectionLabel from './renderSectionLabel'; -import renderTextLine from './renderTextLine'; +import renderLyricLine from './renderLyricLine'; import renderTimeSignature from './renderTimeSignature'; import songTpl from './tpl/song.hbs'; @@ -79,17 +79,12 @@ export default function renderSong( let rendered; if (line.type === lineTypes.CHORD) { - // todo: move this in renderChordLine? let spaced = alignBars ? alignedChordSpacer(line.model, maxBeatsWidth) : simpleChordSpacer(line.model); const nextLine = allFilteredLines[lineIndex + 1]; - if ( - alignChordsWithLyrics && - nextLine && - nextLine.type === lineTypes.TEXT - ) { + if (shouldAlignChords(alignChordsWithLyrics, nextLine)) { const { chordLine, lyricsLine } = chordLyricsSpacer( spaced, nextLine.model @@ -98,7 +93,7 @@ export default function renderSong( spaced = chordLine; } - rendered = renderChordLine(spaced); + rendered = renderChordLineModel(spaced); } else if (line.type === lineTypes.EMPTY_LINE) { rendered = renderEmptyLine(); } else if (line.type === lineTypes.SECTION_LABEL) { @@ -109,7 +104,7 @@ export default function renderSong( } else if (line.type === lineTypes.TIME_SIGNATURE) { rendered = renderTimeSignature(line); } else { - rendered = renderTextLine(line); + rendered = renderLyricLine(line); } return renderLine(rendered, { isFromSectionRepeat: line.isFromSectionRepeat, @@ -133,3 +128,9 @@ export default function renderSong( return songTpl({ song }); } + +function shouldAlignChords(alignChordsWithLyrics, nextLine) { + return ( + alignChordsWithLyrics && nextLine && nextLine.type === lineTypes.LYRIC + ); +} diff --git a/src/renderer/components/renderTextLine.js b/src/renderer/components/renderTextLine.js deleted file mode 100644 index 580088c0..00000000 --- a/src/renderer/components/renderTextLine.js +++ /dev/null @@ -1,9 +0,0 @@ -import textLineTpl from './tpl/textLine.hbs'; - -/** - * @param {LyricLine} textLine - * @returns {String} rendered html - */ -export default function render(textLine) { - return textLineTpl({ textLine: textLine.model.lyrics }); -} diff --git a/src/renderer/components/tpl/lyricLine.hbs b/src/renderer/components/tpl/lyricLine.hbs new file mode 100644 index 00000000..570ec8af --- /dev/null +++ b/src/renderer/components/tpl/lyricLine.hbs @@ -0,0 +1 @@ +{{{ this.lyricLine }}} \ No newline at end of file diff --git a/src/renderer/components/tpl/textLine.hbs b/src/renderer/components/tpl/textLine.hbs deleted file mode 100644 index d72afa24..00000000 --- a/src/renderer/components/tpl/textLine.hbs +++ /dev/null @@ -1 +0,0 @@ -{{{ this.textLine }}} \ No newline at end of file diff --git a/tests/integration/parser/parseSong.spec.js b/tests/integration/parser/parseSong.spec.js index a45d0d1a..bebc14ac 100644 --- a/tests/integration/parser/parseSong.spec.js +++ b/tests/integration/parser/parseSong.spec.js @@ -41,7 +41,7 @@ Let it be`; model: parseChordLine('C.. G..'), }, { - type: 'text', + type: 'lyric', string: 'When I find myself in times of trouble', model: parseLyricLine( 'When I find myself in times of trouble' @@ -53,7 +53,7 @@ Let it be`; model: parseChordLine('Am.. F..'), }, { - type: 'text', + type: 'lyric', string: 'Mother mary comes to me', model: parseLyricLine('Mother mary comes to me'), }, @@ -63,7 +63,7 @@ Let it be`; model: parseChordLine('C.. G..'), }, { - type: 'text', + type: 'lyric', string: 'Speaking words of wisdom', model: parseLyricLine('Speaking words of wisdom'), }, @@ -73,7 +73,7 @@ Let it be`; model: parseChordLine('F. Em. Dm. C.'), }, { - type: 'text', + type: 'lyric', string: 'Let it be', model: parseLyricLine('Let it be'), }, @@ -84,7 +84,7 @@ Let it be`; model: parseChordLine('Am.. G..'), }, { - type: 'text', + type: 'lyric', string: 'Let it be, let it be', model: parseLyricLine('Let it be, let it be'), }, @@ -94,7 +94,7 @@ Let it be`; model: parseChordLine('C.. F..'), }, { - type: 'text', + type: 'lyric', string: 'Let it be, let it be', model: parseLyricLine('Let it be, let it be'), }, @@ -104,7 +104,7 @@ Let it be`; model: parseChordLine('C.. G..'), }, { - type: 'text', + type: 'lyric', string: 'Whispers words of wisdom', model: parseLyricLine('Whispers words of wisdom'), }, @@ -114,7 +114,7 @@ Let it be`; model: parseChordLine('F. Em. Dm. C.'), }, { - type: 'text', + type: 'lyric', string: 'Let it be', model: parseLyricLine('Let it be'), }, @@ -147,7 +147,7 @@ Let it be`; model: parseChordLine('C.. G..'), }, { - type: 'text', + type: 'lyric', string: 'When I find myself in times of trouble', model: parseLyricLine( 'When I find myself in times of trouble' @@ -159,7 +159,7 @@ Let it be`; model: parseChordLine('Am.. F..'), }, { - type: 'text', + type: 'lyric', string: 'Mother mary comes to me', model: parseLyricLine('Mother mary comes to me'), }, diff --git a/tests/unit/parser/songLinesFactory.spec.js b/tests/unit/parser/songLinesFactory.spec.js index d3946408..da35b92c 100644 --- a/tests/unit/parser/songLinesFactory.spec.js +++ b/tests/unit/parser/songLinesFactory.spec.js @@ -54,45 +54,45 @@ Let it be`; const expected = [ { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, { - type: 'text', + type: 'lyric', model: 'When I find myself in times of trouble', string: 'When I find myself in times of trouble', }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, { - type: 'text', + type: 'lyric', model: 'Mother mary comes to me', string: 'Mother mary comes to me', }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, { - type: 'text', + type: 'lyric', model: 'Speaking words of wisdom', string: 'Speaking words of wisdom', }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', model: 'Let it be', string: 'Let it be' }, + { type: 'lyric', model: 'Let it be', string: 'Let it be' }, { type: 'emptyLine', string: '' }, { type: 'chord', string: 'Am.. G..', model: 'Am.. G..' }, { - type: 'text', + type: 'lyric', model: 'Let it be, let it be', string: 'Let it be, let it be', }, { type: 'chord', string: 'C.. F..', model: 'C.. F..' }, { - type: 'text', + type: 'lyric', model: 'Let it be, let it be', string: 'Let it be, let it be', }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, { - type: 'text', + type: 'lyric', model: 'Whispers words of wisdom', string: 'Whispers words of wisdom', }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', model: 'Let it be', string: 'Let it be' }, + { type: 'lyric', model: 'Let it be', string: 'Let it be' }, ]; const songLines = songLinesFactory(); @@ -101,14 +101,14 @@ Let it be`; expect(songLines.asArray()).toEqual(expected); }); - test('Set chordLine as text if parsing fails', () => { + test('Set chordLine as lyric if parsing fails', () => { parseChordLine.mockImplementation((chordLine) => { throw new Error(chordLine); }); const input = 'C. D.. E..'; - const expected = [{ type: 'text', string: input, model: input }]; + const expected = [{ type: 'lyric', string: input, model: input }]; const songLines = songLinesFactory(); input.split('\n').forEach(songLines.addLine); @@ -347,25 +347,25 @@ Let it be`; { type: 'timeSignature', string: '4/4', model: _.cloneDeep(ts4_4) }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, { - type: 'text', + type: 'lyric', model: 'When I find myself in times of trouble', string: 'When I find myself in times of trouble', }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, { - type: 'text', + type: 'lyric', model: 'Mother mary comes to me', string: 'Mother mary comes to me', }, { type: 'emptyLine', string: '' }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, { - type: 'text', + type: 'lyric', model: 'Speaking words of wisdom', string: 'Speaking words of wisdom', }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', model: 'Let it be', string: 'Let it be' }, + { type: 'lyric', model: 'Let it be', string: 'Let it be' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -388,7 +388,7 @@ Let it be`; isFromAutoRepeatChords: true, }, { - type: 'text', + type: 'lyric', model: 'And in my hour of darkness', string: 'And in my hour of darkness', }, @@ -399,7 +399,7 @@ Let it be`; isFromAutoRepeatChords: true, }, { - type: 'text', + type: 'lyric', model: 'she is standing right in front of me', string: 'she is standing right in front of me', }, @@ -411,7 +411,7 @@ Let it be`; isFromAutoRepeatChords: true, }, { - type: 'text', + type: 'lyric', model: 'Speaking words of wisdom', string: 'Speaking words of wisdom', }, @@ -421,7 +421,7 @@ Let it be`; model: 'F. Em. Dm. C.', isFromAutoRepeatChords: true, }, - { type: 'text', model: 'Let it be', string: 'Let it be' }, + { type: 'lyric', model: 'Let it be', string: 'Let it be' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -433,24 +433,24 @@ Let it be`; }, { type: 'chord', string: 'Am.. G..', model: 'Am.. G..' }, { - type: 'text', + type: 'lyric', model: 'Let it be, let it be', string: 'Let it be, let it be', }, { type: 'chord', string: 'C.. F..', model: 'C.. F..' }, { - type: 'text', + type: 'lyric', model: 'Let it be, let it be', string: 'Let it be, let it be', }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, { - type: 'text', + type: 'lyric', model: 'Whispers words of wisdom', string: 'Whispers words of wisdom', }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', model: 'Let it be', string: 'Let it be' }, + { type: 'lyric', model: 'Let it be', string: 'Let it be' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -473,7 +473,7 @@ Let it be`; isFromAutoRepeatChords: true, }, { - type: 'text', + type: 'lyric', model: 'And when the broken hearted people', string: 'And when the broken hearted people', }, @@ -484,7 +484,7 @@ Let it be`; isFromAutoRepeatChords: true, }, { - type: 'text', + type: 'lyric', model: 'Living in the world agree', string: 'Living in the world agree', }, @@ -496,7 +496,7 @@ Let it be`; isFromAutoRepeatChords: true, }, { - type: 'text', + type: 'lyric', model: 'There will be an answer', string: 'There will be an answer', }, @@ -506,7 +506,7 @@ Let it be`; model: 'F. Em. Dm. C.', isFromAutoRepeatChords: true, }, - { type: 'text', model: 'Let it be', string: 'Let it be' }, + { type: 'lyric', model: 'Let it be', string: 'Let it be' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -523,7 +523,7 @@ Let it be`; isFromAutoRepeatChords: true, }, { - type: 'text', + type: 'lyric', model: 'Let it be, let it be', string: 'Let it be, let it be', }, @@ -534,7 +534,7 @@ Let it be`; isFromAutoRepeatChords: true, }, { - type: 'text', + type: 'lyric', model: 'Let it be, let it be', string: 'Let it be, let it be', }, @@ -545,7 +545,7 @@ Let it be`; isFromAutoRepeatChords: true, }, { - type: 'text', + type: 'lyric', model: 'Whispers words of wisdom', string: 'Whispers words of wisdom', }, @@ -555,7 +555,7 @@ Let it be`; model: 'F. Em. Dm. C.', isFromAutoRepeatChords: true, }, - { type: 'text', model: 'Let it be', string: 'Let it be' }, + { type: 'lyric', model: 'Let it be', string: 'Let it be' }, ]; const songLines = songLinesFactory(); @@ -599,13 +599,13 @@ line3-3 id: 'v1', }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1', model: 'line1-1' }, + { type: 'lyric', string: 'line1-1', model: 'line1-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'line1-2', model: 'line1-2' }, + { type: 'lyric', string: 'line1-2', model: 'line1-2' }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-3', model: 'line1-3' }, + { type: 'lyric', string: 'line1-3', model: 'line1-3' }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', string: 'line1-4', model: 'line1-4' }, + { type: 'lyric', string: 'line1-4', model: 'line1-4' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -621,14 +621,14 @@ line3-3 model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line2-1', model: 'line2-1' }, + { type: 'lyric', string: 'line2-1', model: 'line2-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line2-2', model: 'line2-2' }, + { type: 'lyric', string: 'line2-2', model: 'line2-2' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -644,21 +644,21 @@ line3-3 model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line3-1', model: 'line3-1' }, + { type: 'lyric', string: 'line3-1', model: 'line3-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line3-2', model: 'line3-2' }, + { type: 'lyric', string: 'line3-2', model: 'line3-2' }, { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line3-3', model: 'line3-3' }, + { type: 'lyric', string: 'line3-3', model: 'line3-3' }, { type: 'emptyLine', string: '' }, { type: 'emptyLine', string: '' }, { type: 'emptyLine', string: '' }, @@ -697,9 +697,9 @@ line2-4`; id: 'v1', }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1', model: 'line1-1' }, + { type: 'lyric', string: 'line1-1', model: 'line1-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'line1-2', model: 'line1-2' }, + { type: 'lyric', string: 'line1-2', model: 'line1-2' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -715,18 +715,18 @@ line2-4`; model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line2-1', model: 'line2-1' }, + { type: 'lyric', string: 'line2-1', model: 'line2-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line2-2', model: 'line2-2' }, + { type: 'lyric', string: 'line2-2', model: 'line2-2' }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line2-3', model: 'line2-3' }, + { type: 'lyric', string: 'line2-3', model: 'line2-3' }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', string: 'line2-4', model: 'line2-4' }, + { type: 'lyric', string: 'line2-4', model: 'line2-4' }, ]; const songLines = songLinesFactory(); @@ -759,9 +759,9 @@ line2-2`; id: 'v1', }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1', model: 'line1-1' }, + { type: 'lyric', string: 'line1-1', model: 'line1-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'line1-2', model: 'line1-2' }, + { type: 'lyric', string: 'line1-2', model: 'line1-2' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -777,9 +777,9 @@ line2-2`; model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line2-1', model: 'line2-1' }, + { type: 'lyric', string: 'line2-1', model: 'line2-1' }, { type: 'chord', string: 'F. Em. Dm. C.', model: 'F. Em. Dm. C.' }, - { type: 'text', string: 'line2-2', model: 'line2-2' }, + { type: 'lyric', string: 'line2-2', model: 'line2-2' }, ]; const songLines = songLinesFactory(); @@ -804,14 +804,14 @@ line2-1`; const expected = [ { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1', model: 'line1-1' }, + { type: 'lyric', string: 'line1-1', model: 'line1-1' }, { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromChordLineRepeater: true, }, - { type: 'text', string: 'line1-2', model: 'line1-2' }, + { type: 'lyric', string: 'line1-2', model: 'line1-2' }, { type: 'emptyLine', string: '' }, { type: 'emptyLine', string: '' }, { @@ -820,7 +820,7 @@ line2-1`; model: 'C.. G..', isFromChordLineRepeater: true, }, - { type: 'text', string: 'line2-1', model: 'line2-1' }, + { type: 'lyric', string: 'line2-1', model: 'line2-1' }, ]; const songLines = songLinesFactory(); @@ -829,7 +829,7 @@ line2-1`; expect(songLines.asArray()).toEqual(expected); }); - test('should be parsed as text line if there is no chordLine before', () => { + test('should be parsed as lyric line if there is no chordLine before', () => { parseChordLine.mockImplementation((chordLine) => chordLine); const input = `/ @@ -840,12 +840,12 @@ line1-2 line1-3`; const expected = [ - { type: 'text', string: '/', model: '/' }, - { type: 'text', string: 'line1-1', model: 'line1-1' }, - { type: 'text', string: '/', model: '/' }, - { type: 'text', string: 'line1-2', model: 'line1-2' }, - { type: 'text', string: '/', model: '/' }, - { type: 'text', string: 'line1-3', model: 'line1-3' }, + { type: 'lyric', string: '/', model: '/' }, + { type: 'lyric', string: 'line1-1', model: 'line1-1' }, + { type: 'lyric', string: '/', model: '/' }, + { type: 'lyric', string: 'line1-2', model: 'line1-2' }, + { type: 'lyric', string: '/', model: '/' }, + { type: 'lyric', string: 'line1-3', model: 'line1-3' }, ]; const songLines = songLinesFactory(); @@ -878,9 +878,9 @@ line2-2`; id: 'v1', }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1', model: 'line1-1' }, + { type: 'lyric', string: 'line1-1', model: 'line1-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'line1-2', model: 'line1-2' }, + { type: 'lyric', string: 'line1-2', model: 'line1-2' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -896,14 +896,14 @@ line2-2`; model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line2-1', model: 'line2-1' }, + { type: 'lyric', string: 'line2-1', model: 'line2-1' }, { type: 'chord', string: 'C.. G..', model: 'C.. G..', isFromChordLineRepeater: true, }, - { type: 'text', string: 'line2-2', model: 'line2-2' }, + { type: 'lyric', string: 'line2-2', model: 'line2-2' }, ]; const songLines = songLinesFactory(); @@ -933,9 +933,9 @@ line1-2 id: 'v1', }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1', model: 'line1-1' }, + { type: 'lyric', string: 'line1-1', model: 'line1-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'line1-2', model: 'line1-2' }, + { type: 'lyric', string: 'line1-2', model: 'line1-2' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -953,7 +953,7 @@ line1-2 isFromSectionRepeat: true, }, { - type: 'text', + type: 'lyric', string: 'line1-1', model: 'line1-1', isFromSectionRepeat: true, @@ -965,7 +965,7 @@ line1-2 isFromSectionRepeat: true, }, { - type: 'text', + type: 'lyric', string: 'line1-2', model: 'line1-2', isFromSectionRepeat: true, @@ -1004,9 +1004,9 @@ line2-3 id: 'v1', }, { type: 'chord', string: 'C.. G..', model: 'C.. G..' }, - { type: 'text', string: 'line1-1', model: 'line1-1' }, + { type: 'lyric', string: 'line1-1', model: 'line1-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..' }, - { type: 'text', string: 'line1-2', model: 'line1-2' }, + { type: 'lyric', string: 'line1-2', model: 'line1-2' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -1022,21 +1022,21 @@ line2-3 model: 'C.. G..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line2-1', model: 'line2-1' }, + { type: 'lyric', string: 'line2-1', model: 'line2-1' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromAutoRepeatChords: true, }, - { type: 'text', string: 'line2-2', model: 'line2-2' }, + { type: 'lyric', string: 'line2-2', model: 'line2-2' }, { type: 'chord', string: 'Am.. F..', model: 'Am.. F..', isFromChordLineRepeater: true, }, - { type: 'text', string: 'line2-3', model: 'line2-3' }, + { type: 'lyric', string: 'line2-3', model: 'line2-3' }, { type: 'emptyLine', string: '' }, { type: 'sectionLabel', @@ -1055,7 +1055,7 @@ line2-3 isFromAutoRepeatChords: true, }, { - type: 'text', + type: 'lyric', string: 'line2-1', model: 'line2-1', isFromSectionRepeat: true, @@ -1068,7 +1068,7 @@ line2-3 isFromAutoRepeatChords: true, }, { - type: 'text', + type: 'lyric', string: 'line2-2', model: 'line2-2', isFromSectionRepeat: true, @@ -1081,7 +1081,7 @@ line2-3 isFromChordLineRepeater: true, }, { - type: 'text', + type: 'lyric', string: 'line2-3', model: 'line2-3', isFromSectionRepeat: true, diff --git a/tests/unit/renderer/components/renderTextLine.spec.js b/tests/unit/renderer/components/renderLyricLine.spec.js similarity index 58% rename from tests/unit/renderer/components/renderTextLine.spec.js rename to tests/unit/renderer/components/renderLyricLine.spec.js index 12e91a4a..1748d6ec 100644 --- a/tests/unit/renderer/components/renderTextLine.spec.js +++ b/tests/unit/renderer/components/renderLyricLine.spec.js @@ -1,13 +1,13 @@ -import renderTextLine from '../../../../src/renderer/components/renderTextLine'; +import renderLyricLine from '../../../../src/renderer/components/renderLyricLine'; import htmlToElement from '../../../../src/core/dom/htmlToElement'; -describe('renderTextLine', () => { +describe('renderLyricLine', () => { test('Module', () => { - expect(renderTextLine).toBeInstanceOf(Function); + expect(renderLyricLine).toBeInstanceOf(Function); }); test('Should return valid html', () => { - const rendered = renderTextLine({ + const rendered = renderLyricLine({ string: 'textContent', model: { lyrics: 'textContent', @@ -18,6 +18,6 @@ describe('renderTextLine', () => { expect(element).toBeInstanceOf(Node); expect(element.nodeName).toBe('SPAN'); - expect(element.classList.contains('cmTextLine')).toBe(true); + expect(element.classList.contains('cmLyricLine')).toBe(true); }); }); diff --git a/tests/unit/renderer/spacers/chord/chordLyrics.spec.js b/tests/unit/renderer/spacers/chord/chordLyrics.spec.js index 8a3038ae..09b6f0d1 100644 --- a/tests/unit/renderer/spacers/chord/chordLyrics.spec.js +++ b/tests/unit/renderer/spacers/chord/chordLyrics.spec.js @@ -4,7 +4,7 @@ import { forEachChordInChordLine } from '../../../../../src/parser/helper/songs' import chordLyricsSpacer from '../../../../../src/renderer/spacers/chord/chordLyrics'; import parseLyricLine from '../../../../../src/parser/parseLyricLine'; import parseChordLine from '../../../../../src/parser/parseChordLine'; -import renderTextLine from '../../../../../src/renderer/components/renderTextLine'; +import renderLyricLine from '../../../../../src/renderer/components/renderLyricLine'; import renderChordLine from '../../../../../src/renderer/components/renderChordLine'; import getChordSymbol from '../../../../../src/renderer/helpers/getChordSymbol'; @@ -124,7 +124,7 @@ describe.each([ ); const renderedChords = renderChordLine(chordLine); - const renderedLyrics = renderTextLine({ model: lyricsLine }); + const renderedLyrics = renderLyricLine({ model: lyricsLine }); expect(stripTags(renderedChords)).toEqual(chordsLineOutput); expect(stripTags(renderedLyrics)).toEqual(LyricsLineOutput); From ea9986c4c4910dd22e664482b11ef3f8662ca1e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christophe=20No=C3=ABl?= Date: Sun, 29 Aug 2021 20:41:19 +0200 Subject: [PATCH 09/11] rename syntax.js to symbols.js --- src/renderer/components/renderChordLine.js | 4 ++-- src/renderer/spacers/chord/chordLyrics.js | 10 +++++----- src/renderer/{syntax.js => symbols.js} | 0 3 files changed, 7 insertions(+), 7 deletions(-) rename src/renderer/{syntax.js => symbols.js} (100%) diff --git a/src/renderer/components/renderChordLine.js b/src/renderer/components/renderChordLine.js index 46d74d97..08972ecc 100644 --- a/src/renderer/components/renderChordLine.js +++ b/src/renderer/components/renderChordLine.js @@ -2,7 +2,7 @@ import chordLineTpl from './tpl/chordLine.hbs'; import renderBarContent from './renderBarContent'; import barSeparatorTpl from './tpl/barSeparator.hbs'; -import syntax from '../syntax'; +import symbols from '../symbols'; /** * @param {ChordLine} chordLineModel @@ -18,7 +18,7 @@ export default function renderChordLine(chordLineModel) { const chordLine = barSeparator + allBarsRendered.join(barSeparator) + barSeparator; - const chordLineOffset = syntax.chordLineOffsetSpacer.repeat( + const chordLineOffset = symbols.chordLineOffsetSpacer.repeat( chordLineModel.offset || 0 ); diff --git a/src/renderer/spacers/chord/chordLyrics.js b/src/renderer/spacers/chord/chordLyrics.js index ace2d57e..6f2f9f95 100644 --- a/src/renderer/spacers/chord/chordLyrics.js +++ b/src/renderer/spacers/chord/chordLyrics.js @@ -1,7 +1,7 @@ import _cloneDeep from 'lodash/cloneDeep'; import { forEachChordInChordLine } from '../../../parser/helper/songs'; -import syntax from '../../syntax'; //fixme rename +import symbols from '../../symbols'; const chordSpaceAfterDefault = 1; @@ -40,15 +40,15 @@ export default function space(chordLineInput, lyricsLineInput) { if (lyricToken) { chordToken = chord.symbol; if (isFirstChord(barIndex, chordIndex)) { - chordToken = syntax.barSeparator + chordToken; + chordToken = symbols.barSeparator + chordToken; } else if (isNewBar(currentBarIndex, barIndex)) { - chordToken = syntax.barSeparator + chordToken; + chordToken = symbols.barSeparator + chordToken; currentBarIndex = barIndex; } if (startsWithSpace(lyricToken)) { lyricToken = - syntax.lyricsSpacer.repeat(chordToken.length) + + symbols.lyricsSpacer.repeat(chordToken.length) + lyricToken; } @@ -60,7 +60,7 @@ export default function space(chordLineInput, lyricsLineInput) { chordToken.length - lyricToken.length + chordSpaceAfterDefault; - lyricToken += syntax.lyricsSpacer.repeat(lyricsSpaceAfter); + lyricToken += symbols.lyricsSpacer.repeat(lyricsSpaceAfter); } spacedLyricsLine += lyricToken; chord.spacesWithin = 0; diff --git a/src/renderer/syntax.js b/src/renderer/symbols.js similarity index 100% rename from src/renderer/syntax.js rename to src/renderer/symbols.js From 524b889b1870e891b8cddec18c57311a1c4198ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christophe=20No=C3=ABl?= Date: Sun, 29 Aug 2021 20:58:17 +0200 Subject: [PATCH 10/11] reset spaces after for remaining chords in line --- src/renderer/spacers/chord/chordLyrics.js | 4 +++- tests/unit/renderer/spacers/chord/chordLyrics.spec.js | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/renderer/spacers/chord/chordLyrics.js b/src/renderer/spacers/chord/chordLyrics.js index 6f2f9f95..79f2c15a 100644 --- a/src/renderer/spacers/chord/chordLyrics.js +++ b/src/renderer/spacers/chord/chordLyrics.js @@ -63,8 +63,10 @@ export default function space(chordLineInput, lyricsLineInput) { lyricToken += symbols.lyricsSpacer.repeat(lyricsSpaceAfter); } spacedLyricsLine += lyricToken; - chord.spacesWithin = 0; + } else { + chord.spacesAfter = chordSpaceAfterDefault; } + chord.spacesWithin = 0; } ); diff --git a/tests/unit/renderer/spacers/chord/chordLyrics.spec.js b/tests/unit/renderer/spacers/chord/chordLyrics.spec.js index 09b6f0d1..f058a1eb 100644 --- a/tests/unit/renderer/spacers/chord/chordLyrics.spec.js +++ b/tests/unit/renderer/spacers/chord/chordLyrics.spec.js @@ -65,10 +65,10 @@ describe.each([ 'Put me on top', ], [ - 'Spacing of extra chords should be kept (2 by default)', + 'Spacing of extra chords should be reset to 1 (default spacing for chordLyricsSpacer)', 'A7 B C7 B7 F7', '_Put me _on top', - '|A7 |B |C7 |B7 |F7 |', + '|A7 |B |C7 |B7 |F7 |', 'Put me on top', ], [ @@ -82,7 +82,7 @@ describe.each([ 'Edge case: no lyrics, single character chord!', 'A B C D', '_ _ _ _', - '|A |B |C |D |', + '|A |B |C |D |', '', ], [ From 96e455bf608bba0dae630ed8e2d9a9606a5198bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christophe=20No=C3=ABl?= Date: Sun, 29 Aug 2021 22:19:16 +0200 Subject: [PATCH 11/11] update dependencies and build --- SLOC | 36 +- lib/chord-mark.js | 722 +++++++++++---- lib/chord-mark.js.map | 2 +- package-lock.json | 2051 +++++++++++++++++++---------------------- package.json | 33 +- 5 files changed, 1537 insertions(+), 1307 deletions(-) diff --git a/SLOC b/SLOC index fad69e9b..86c25575 100644 --- a/SLOC +++ b/SLOC @@ -2,17 +2,17 @@ Source code: ---------- Result ------------ - Physical : 1352 - Source : 900 - Comment : 235 - Single-line comment : 15 - Block comment : 220 + Physical : 1556 + Source : 1088 + Comment : 253 + Single-line comment : 16 + Block comment : 237 Mixed : 14 Empty block comment : 0 - Empty : 231 + Empty : 229 To Do : 0 -Number of files read : 49 +Number of files read : 52 ---------------------------- @@ -20,17 +20,17 @@ Tests: ---------- Result ------------ - Physical : 3027 - Source : 2554 + Physical : 4261 + Source : 3851 Comment : 13 Single-line comment : 13 Block comment : 0 Mixed : 12 Empty block comment : 0 - Empty : 472 + Empty : 409 To Do : 0 -Number of files read : 37 +Number of files read : 39 ---------------------------- @@ -38,17 +38,17 @@ Total: ---------- Result ------------ - Physical : 4379 - Source : 3454 - Comment : 248 - Single-line comment : 28 - Block comment : 220 + Physical : 5817 + Source : 4939 + Comment : 266 + Single-line comment : 29 + Block comment : 237 Mixed : 26 Empty block comment : 0 - Empty : 703 + Empty : 638 To Do : 0 -Number of files read : 86 +Number of files read : 91 ---------------------------- diff --git a/lib/chord-mark.js b/lib/chord-mark.js index 2bdb8c2b..486687aa 100644 --- a/lib/chord-mark.js +++ b/lib/chord-mark.js @@ -12061,6 +12061,7 @@ var callWithSafeIterationClosing = __webpack_require__(3411); var isArrayIteratorMethod = __webpack_require__(7659); var toLength = __webpack_require__(7466); var createProperty = __webpack_require__(6135); +var getIterator = __webpack_require__(8554); var getIteratorMethod = __webpack_require__(1246); // `Array.from` method implementation @@ -12077,7 +12078,7 @@ module.exports = function from(arrayLike /* , mapfn = undefined, thisArg = undef if (mapping) mapfn = bind(mapfn, argumentsLength > 2 ? arguments[2] : undefined, 2); // if the target is not iterable or it's an array with the default iterator - use a simple case if (iteratorMethod != undefined && !(C == Array && isArrayIteratorMethod(iteratorMethod))) { - iterator = iteratorMethod.call(O); + iterator = getIterator(O, iteratorMethod); next = iterator.next; result = new C(); for (;!(step = next.call(iterator)).done; index++) { @@ -12149,14 +12150,14 @@ var arraySpeciesCreate = __webpack_require__(5417); var push = [].push; -// `Array.prototype.{ forEach, map, filter, some, every, find, findIndex, filterOut }` methods implementation +// `Array.prototype.{ forEach, map, filter, some, every, find, findIndex, filterReject }` methods implementation var createMethod = function (TYPE) { var IS_MAP = TYPE == 1; var IS_FILTER = TYPE == 2; var IS_SOME = TYPE == 3; var IS_EVERY = TYPE == 4; var IS_FIND_INDEX = TYPE == 6; - var IS_FILTER_OUT = TYPE == 7; + var IS_FILTER_REJECT = TYPE == 7; var NO_HOLES = TYPE == 5 || IS_FIND_INDEX; return function ($this, callbackfn, that, specificCreate) { var O = toObject($this); @@ -12165,7 +12166,7 @@ var createMethod = function (TYPE) { var length = toLength(self.length); var index = 0; var create = specificCreate || arraySpeciesCreate; - var target = IS_MAP ? create($this, length) : IS_FILTER || IS_FILTER_OUT ? create($this, 0) : undefined; + var target = IS_MAP ? create($this, length) : IS_FILTER || IS_FILTER_REJECT ? create($this, 0) : undefined; var value, result; for (;length > index; index++) if (NO_HOLES || index in self) { value = self[index]; @@ -12179,7 +12180,7 @@ var createMethod = function (TYPE) { case 2: push.call(target, value); // filter } else switch (TYPE) { case 4: return false; // every - case 7: push.call(target, value); // filterOut + case 7: push.call(target, value); // filterReject } } } @@ -12209,9 +12210,9 @@ module.exports = { // `Array.prototype.findIndex` method // https://tc39.es/ecma262/#sec-array.prototype.findIndex findIndex: createMethod(6), - // `Array.prototype.filterOut` method + // `Array.prototype.filterReject` method // https://github.com/tc39/proposal-array-filtering - filterOut: createMethod(7) + filterReject: createMethod(7) }; @@ -12261,7 +12262,7 @@ module.exports = function (METHOD_NAME, argument) { /***/ }), -/***/ 5417: +/***/ 7475: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { var isObject = __webpack_require__(111); @@ -12270,9 +12271,9 @@ var wellKnownSymbol = __webpack_require__(5112); var SPECIES = wellKnownSymbol('species'); -// `ArraySpeciesCreate` abstract operation +// a part of `ArraySpeciesCreate` abstract operation // https://tc39.es/ecma262/#sec-arrayspeciescreate -module.exports = function (originalArray, length) { +module.exports = function (originalArray) { var C; if (isArray(originalArray)) { C = originalArray.constructor; @@ -12282,7 +12283,21 @@ module.exports = function (originalArray, length) { C = C[SPECIES]; if (C === null) C = undefined; } - } return new (C === undefined ? Array : C)(length === 0 ? 0 : length); + } return C === undefined ? Array : C; +}; + + +/***/ }), + +/***/ 5417: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var arraySpeciesConstructor = __webpack_require__(7475); + +// `ArraySpeciesCreate` abstract operation +// https://tc39.es/ecma262/#sec-arrayspeciescreate +module.exports = function (originalArray, length) { + return new (arraySpeciesConstructor(originalArray))(length === 0 ? 0 : length); }; @@ -12299,8 +12314,7 @@ module.exports = function (iterator, fn, value, ENTRIES) { try { return ENTRIES ? fn(anObject(value)[0], value[1]) : fn(value); } catch (error) { - iteratorClose(iterator); - throw error; + iteratorClose(iterator, 'throw', error); } }; @@ -12663,7 +12677,7 @@ module.exports = function (CONSTRUCTOR_NAME, wrapper, common) { if (REPLACE) { // create collection constructor Constructor = common.getConstructor(wrapper, CONSTRUCTOR_NAME, IS_MAP, ADDER); - InternalMetadataModule.REQUIRED = true; + InternalMetadataModule.enable(); } else if (isForced(CONSTRUCTOR_NAME, true)) { var instance = new Constructor(); // early implementations not supports chaining @@ -12815,12 +12829,12 @@ module.exports = function (bitmap, value) { "use strict"; -var toPrimitive = __webpack_require__(7593); +var toPropertyKey = __webpack_require__(4948); var definePropertyModule = __webpack_require__(3070); var createPropertyDescriptor = __webpack_require__(9114); module.exports = function (object, key, value) { - var propertyKey = toPrimitive(key); + var propertyKey = toPropertyKey(key); if (propertyKey in object) definePropertyModule.f(object, propertyKey, createPropertyDescriptor(0, value)); else object[propertyKey] = value; }; @@ -13034,7 +13048,8 @@ var global = __webpack_require__(7854); var userAgent = __webpack_require__(8113); var process = global.process; -var versions = process && process.versions; +var Deno = global.Deno; +var versions = process && process.versions || Deno && Deno.version; var v8 = versions && versions.v8; var match, version; @@ -13308,7 +13323,6 @@ module.exports = Function.bind || function bind(that /* , ...args */) { /***/ 5005: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { -var path = __webpack_require__(857); var global = __webpack_require__(7854); var aFunction = function (variable) { @@ -13316,8 +13330,7 @@ var aFunction = function (variable) { }; module.exports = function (namespace, method) { - return arguments.length < 2 ? aFunction(path[namespace]) || aFunction(global[namespace]) - : path[namespace] && path[namespace][method] || global[namespace] && global[namespace][method]; + return arguments.length < 2 ? aFunction(global[namespace]) : global[namespace] && global[namespace][method]; }; @@ -13339,6 +13352,22 @@ module.exports = function (it) { }; +/***/ }), + +/***/ 8554: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var anObject = __webpack_require__(9670); +var getIteratorMethod = __webpack_require__(1246); + +module.exports = function (it, usingIterator) { + var iteratorMethod = arguments.length < 2 ? getIteratorMethod(it) : usingIterator; + if (typeof iteratorMethod != 'function') { + throw TypeError(String(it) + ' is not iterable'); + } return anObject(iteratorMethod.call(it)); +}; + + /***/ }), /***/ 647: @@ -13526,13 +13555,17 @@ module.exports = store.inspectSource; /***/ 2423: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { +var $ = __webpack_require__(2109); var hiddenKeys = __webpack_require__(3501); var isObject = __webpack_require__(111); var has = __webpack_require__(6656); var defineProperty = __webpack_require__(3070).f; +var getOwnPropertyNamesModule = __webpack_require__(8006); +var getOwnPropertyNamesExternalModule = __webpack_require__(1156); var uid = __webpack_require__(9711); var FREEZING = __webpack_require__(6677); +var REQUIRED = false; var METADATA = uid('meta'); var id = 0; @@ -13576,12 +13609,38 @@ var getWeakData = function (it, create) { // add metadata on freeze-family methods calling var onFreeze = function (it) { - if (FREEZING && meta.REQUIRED && isExtensible(it) && !has(it, METADATA)) setMetadata(it); + if (FREEZING && REQUIRED && isExtensible(it) && !has(it, METADATA)) setMetadata(it); return it; }; +var enable = function () { + meta.enable = function () { /* empty */ }; + REQUIRED = true; + var getOwnPropertyNames = getOwnPropertyNamesModule.f; + var splice = [].splice; + var test = {}; + test[METADATA] = 1; + + // prevent exposing of metadata key + if (getOwnPropertyNames(test).length) { + getOwnPropertyNamesModule.f = function (it) { + var result = getOwnPropertyNames(it); + for (var i = 0, length = result.length; i < length; i++) { + if (result[i] === METADATA) { + splice.call(result, i, 1); + break; + } + } return result; + }; + + $({ target: 'Object', stat: true, forced: true }, { + getOwnPropertyNames: getOwnPropertyNamesExternalModule.f + }); + } +}; + var meta = module.exports = { - REQUIRED: false, + enable: enable, fastKey: fastKey, getWeakData: getWeakData, onFreeze: onFreeze @@ -13761,6 +13820,22 @@ module.exports = function (it) { }; +/***/ }), + +/***/ 2190: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var getBuiltIn = __webpack_require__(5005); +var USE_SYMBOL_AS_UID = __webpack_require__(3307); + +module.exports = USE_SYMBOL_AS_UID ? function (it) { + return typeof it == 'symbol'; +} : function (it) { + var $Symbol = getBuiltIn('Symbol'); + return typeof $Symbol == 'function' && Object(it) instanceof $Symbol; +}; + + /***/ }), /***/ 408: @@ -13770,6 +13845,7 @@ var anObject = __webpack_require__(9670); var isArrayIteratorMethod = __webpack_require__(7659); var toLength = __webpack_require__(7466); var bind = __webpack_require__(9974); +var getIterator = __webpack_require__(8554); var getIteratorMethod = __webpack_require__(1246); var iteratorClose = __webpack_require__(9212); @@ -13787,7 +13863,7 @@ module.exports = function (iterable, unboundFunction, options) { var iterator, iterFn, index, length, result, next, step; var stop = function (condition) { - if (iterator) iteratorClose(iterator); + if (iterator) iteratorClose(iterator, 'return', condition); return new Result(true, condition); }; @@ -13810,7 +13886,7 @@ module.exports = function (iterable, unboundFunction, options) { if (result && result instanceof Result) return result; } return new Result(false); } - iterator = iterFn.call(iterable); + iterator = getIterator(iterable, iterFn); } next = iterator.next; @@ -13818,8 +13894,7 @@ module.exports = function (iterable, unboundFunction, options) { try { result = callFn(step.value); } catch (error) { - iteratorClose(iterator); - throw error; + iteratorClose(iterator, 'throw', error); } if (typeof result == 'object' && result && result instanceof Result) return result; } return new Result(false); @@ -13833,11 +13908,24 @@ module.exports = function (iterable, unboundFunction, options) { var anObject = __webpack_require__(9670); -module.exports = function (iterator) { - var returnMethod = iterator['return']; - if (returnMethod !== undefined) { - return anObject(returnMethod.call(iterator)).value; +module.exports = function (iterator, kind, value) { + var innerResult, innerError; + anObject(iterator); + try { + innerResult = iterator['return']; + if (innerResult === undefined) { + if (kind === 'throw') throw value; + return value; + } + innerResult = innerResult.call(iterator); + } catch (error) { + innerError = true; + innerResult = error; } + if (kind === 'throw') throw value; + if (innerError) throw innerResult; + anObject(innerResult); + return value; }; @@ -13942,6 +14030,7 @@ module.exports = typeof WeakMap === 'function' && /native code/.test(inspectSour /***/ ((module, __unused_webpack_exports, __webpack_require__) => { var global = __webpack_require__(7854); +var toString = __webpack_require__(1340); var trim = __webpack_require__(3111).trim; var whitespaces = __webpack_require__(1361); @@ -13952,7 +14041,7 @@ var FORCED = $parseInt(whitespaces + '08') !== 8 || $parseInt(whitespaces + '0x1 // `parseInt` method // https://tc39.es/ecma262/#sec-parseint-string-radix module.exports = FORCED ? function parseInt(string, radix) { - var S = trim(String(string)); + var S = trim(toString(string)); return $parseInt(S, (radix >>> 0) || (hex.test(S) ? 16 : 10)); } : $parseInt; @@ -13962,6 +14051,7 @@ module.exports = FORCED ? function parseInt(string, radix) { /***/ 30: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { +/* global ActiveXObject -- old IE, WSH */ var anObject = __webpack_require__(9670); var defineProperties = __webpack_require__(6048); var enumBugKeys = __webpack_require__(748); @@ -14016,10 +14106,13 @@ var NullProtoObjectViaIFrame = function () { var activeXDocument; var NullProtoObject = function () { try { - /* global ActiveXObject -- old IE */ - activeXDocument = document.domain && new ActiveXObject('htmlfile'); + activeXDocument = new ActiveXObject('htmlfile'); } catch (error) { /* ignore */ } - NullProtoObject = activeXDocument ? NullProtoObjectViaActiveX(activeXDocument) : NullProtoObjectViaIFrame(); + NullProtoObject = typeof document != 'undefined' + ? document.domain && activeXDocument + ? NullProtoObjectViaActiveX(activeXDocument) // old IE + : NullProtoObjectViaIFrame() + : NullProtoObjectViaActiveX(activeXDocument); // WSH var length = enumBugKeys.length; while (length--) delete NullProtoObject[PROTOTYPE][enumBugKeys[length]]; return NullProtoObject(); @@ -14074,7 +14167,7 @@ module.exports = DESCRIPTORS ? Object.defineProperties : function defineProperti var DESCRIPTORS = __webpack_require__(9781); var IE8_DOM_DEFINE = __webpack_require__(4664); var anObject = __webpack_require__(9670); -var toPrimitive = __webpack_require__(7593); +var toPropertyKey = __webpack_require__(4948); // eslint-disable-next-line es/no-object-defineproperty -- safe var $defineProperty = Object.defineProperty; @@ -14083,7 +14176,7 @@ var $defineProperty = Object.defineProperty; // https://tc39.es/ecma262/#sec-object.defineproperty exports.f = DESCRIPTORS ? $defineProperty : function defineProperty(O, P, Attributes) { anObject(O); - P = toPrimitive(P, true); + P = toPropertyKey(P); anObject(Attributes); if (IE8_DOM_DEFINE) try { return $defineProperty(O, P, Attributes); @@ -14103,7 +14196,7 @@ var DESCRIPTORS = __webpack_require__(9781); var propertyIsEnumerableModule = __webpack_require__(5296); var createPropertyDescriptor = __webpack_require__(9114); var toIndexedObject = __webpack_require__(5656); -var toPrimitive = __webpack_require__(7593); +var toPropertyKey = __webpack_require__(4948); var has = __webpack_require__(6656); var IE8_DOM_DEFINE = __webpack_require__(4664); @@ -14114,7 +14207,7 @@ var $getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; // https://tc39.es/ecma262/#sec-object.getownpropertydescriptor exports.f = DESCRIPTORS ? $getOwnPropertyDescriptor : function getOwnPropertyDescriptor(O, P) { O = toIndexedObject(O); - P = toPrimitive(P, true); + P = toPropertyKey(P); if (IE8_DOM_DEFINE) try { return $getOwnPropertyDescriptor(O, P); } catch (error) { /* empty */ } @@ -14316,6 +14409,24 @@ module.exports = TO_STRING_TAG_SUPPORT ? {}.toString : function toString() { }; +/***/ }), + +/***/ 2140: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var isObject = __webpack_require__(111); + +// `OrdinaryToPrimitive` abstract operation +// https://tc39.es/ecma262/#sec-ordinarytoprimitive +module.exports = function (input, pref) { + var fn, val; + if (pref === 'string' && typeof (fn = input.toString) == 'function' && !isObject(val = fn.call(input))) return val; + if (typeof (fn = input.valueOf) == 'function' && !isObject(val = fn.call(input))) return val; + if (pref !== 'string' && typeof (fn = input.toString) == 'function' && !isObject(val = fn.call(input))) return val; + throw TypeError("Can't convert object to primitive value"); +}; + + /***/ }), /***/ 3887: @@ -14440,8 +14551,9 @@ module.exports = function (R, S) { "use strict"; -/* eslint-disable regexp/no-assertion-capturing-group, regexp/no-empty-group, regexp/no-lazy-ends -- testing */ +/* eslint-disable regexp/no-empty-capturing-group, regexp/no-empty-group, regexp/no-lazy-ends -- testing */ /* eslint-disable regexp/no-useless-quantifier -- testing */ +var toString = __webpack_require__(1340); var regexpFlags = __webpack_require__(7066); var stickyHelpers = __webpack_require__(2999); var shared = __webpack_require__(2309); @@ -14472,9 +14584,10 @@ var PATCH = UPDATES_LAST_INDEX_WRONG || NPCG_INCLUDED || UNSUPPORTED_Y || UNSUPP if (PATCH) { // eslint-disable-next-line max-statements -- TODO - patchedExec = function exec(str) { + patchedExec = function exec(string) { var re = this; var state = getInternalState(re); + var str = toString(string); var raw = state.raw; var result, reCopy, lastIndex, match, i, object, group; @@ -14498,9 +14611,9 @@ if (PATCH) { flags += 'g'; } - strCopy = String(str).slice(re.lastIndex); + strCopy = str.slice(re.lastIndex); // Support anchored sticky behavior. - if (re.lastIndex > 0 && (!re.multiline || re.multiline && str[re.lastIndex - 1] !== '\n')) { + if (re.lastIndex > 0 && (!re.multiline || re.multiline && str.charAt(re.lastIndex - 1) !== '\n')) { source = '(?: ' + source + ')'; strCopy = ' ' + strCopy; charsAdded++; @@ -14582,21 +14695,20 @@ module.exports = function () { /***/ ((__unused_webpack_module, exports, __webpack_require__) => { var fails = __webpack_require__(7293); +var global = __webpack_require__(7854); -// babel-minify transpiles RegExp('a', 'y') -> /a/y and it causes SyntaxError, -var RE = function (s, f) { - return RegExp(s, f); -}; +// babel-minify and Closure Compiler transpiles RegExp('a', 'y') -> /a/y and it causes SyntaxError +var $RegExp = global.RegExp; exports.UNSUPPORTED_Y = fails(function () { - var re = RE('a', 'y'); + var re = $RegExp('a', 'y'); re.lastIndex = 2; return re.exec('abcd') != null; }); exports.BROKEN_CARET = fails(function () { // https://bugzilla.mozilla.org/show_bug.cgi?id=773687 - var re = RE('^r', 'gy'); + var re = $RegExp('^r', 'gy'); re.lastIndex = 2; return re.exec('str') != null; }); @@ -14608,10 +14720,13 @@ exports.BROKEN_CARET = fails(function () { /***/ ((module, __unused_webpack_exports, __webpack_require__) => { var fails = __webpack_require__(7293); +var global = __webpack_require__(7854); + +// babel-minify and Closure Compiler transpiles RegExp('.', 's') -> /./s and it causes SyntaxError +var $RegExp = global.RegExp; module.exports = fails(function () { - // babel-minify transpiles RegExp('.', 's') -> /./s and it causes SyntaxError - var re = RegExp('.', (typeof '').charAt(0)); + var re = $RegExp('.', 's'); return !(re.dotAll && re.exec('\n') && re.flags === 's'); }); @@ -14622,10 +14737,13 @@ module.exports = fails(function () { /***/ ((module, __unused_webpack_exports, __webpack_require__) => { var fails = __webpack_require__(7293); +var global = __webpack_require__(7854); + +// babel-minify and Closure Compiler transpiles RegExp('(?b)', 'g') -> /(?b)/g and it causes SyntaxError +var $RegExp = global.RegExp; module.exports = fails(function () { - // babel-minify transpiles RegExp('.', 'g') -> /./g and it causes SyntaxError - var re = RegExp('(?b)', (typeof '').charAt(5)); + var re = $RegExp('(?b)', 'g'); return re.exec('b').groups.a !== 'b' || 'b'.replace(re, '$c') !== 'bc'; }); @@ -14650,11 +14768,11 @@ module.exports = function (it) { /***/ ((module, __unused_webpack_exports, __webpack_require__) => { var global = __webpack_require__(7854); -var createNonEnumerableProperty = __webpack_require__(8880); module.exports = function (key, value) { try { - createNonEnumerableProperty(global, key, value); + // eslint-disable-next-line es/no-object-defineproperty -- safe + Object.defineProperty(global, key, { value: value, configurable: true, writable: true }); } catch (error) { global[key] = value; } return value; @@ -14746,7 +14864,7 @@ var store = __webpack_require__(5465); (module.exports = function (key, value) { return store[key] || (store[key] = value !== undefined ? value : {}); })('versions', []).push({ - version: '3.15.2', + version: '3.16.4', mode: IS_PURE ? 'pure' : 'global', copyright: '© 2021 Denis Pushkarev (zloirock.ru)' }); @@ -14778,12 +14896,13 @@ module.exports = function (O, defaultConstructor) { /***/ ((module, __unused_webpack_exports, __webpack_require__) => { var toInteger = __webpack_require__(9958); +var toString = __webpack_require__(1340); var requireObjectCoercible = __webpack_require__(4488); -// `String.prototype.{ codePointAt, at }` methods implementation +// `String.prototype.codePointAt` methods implementation var createMethod = function (CONVERT_TO_STRING) { return function ($this, pos) { - var S = String(requireObjectCoercible($this)); + var S = toString(requireObjectCoercible($this)); var position = toInteger(pos); var size = S.length; var first, second; @@ -14814,12 +14933,13 @@ module.exports = { "use strict"; var toInteger = __webpack_require__(9958); +var toString = __webpack_require__(1340); var requireObjectCoercible = __webpack_require__(4488); // `String.prototype.repeat` method implementation // https://tc39.es/ecma262/#sec-string.prototype.repeat module.exports = function repeat(count) { - var str = String(requireObjectCoercible(this)); + var str = toString(requireObjectCoercible(this)); var result = ''; var n = toInteger(count); if (n < 0 || n == Infinity) throw RangeError('Wrong number of repetitions'); @@ -14853,6 +14973,7 @@ module.exports = function (METHOD_NAME) { /***/ ((module, __unused_webpack_exports, __webpack_require__) => { var requireObjectCoercible = __webpack_require__(4488); +var toString = __webpack_require__(1340); var whitespaces = __webpack_require__(1361); var whitespace = '[' + whitespaces + ']'; @@ -14862,7 +14983,7 @@ var rtrim = RegExp(whitespace + whitespace + '*$'); // `String.prototype.{ trim, trimStart, trimEnd, trimLeft, trimRight }` methods implementation var createMethod = function (TYPE) { return function ($this) { - var string = String(requireObjectCoercible($this)); + var string = toString(requireObjectCoercible($this)); if (TYPE & 1) string = string.replace(ltrim, ''); if (TYPE & 2) string = string.replace(rtrim, ''); return string; @@ -14966,18 +15087,42 @@ module.exports = function (argument) { /***/ ((module, __unused_webpack_exports, __webpack_require__) => { var isObject = __webpack_require__(111); +var isSymbol = __webpack_require__(2190); +var ordinaryToPrimitive = __webpack_require__(2140); +var wellKnownSymbol = __webpack_require__(5112); + +var TO_PRIMITIVE = wellKnownSymbol('toPrimitive'); // `ToPrimitive` abstract operation // https://tc39.es/ecma262/#sec-toprimitive -// instead of the ES6 spec version, we didn't implement @@toPrimitive case -// and the second argument - flag - preferred type is a string -module.exports = function (input, PREFERRED_STRING) { - if (!isObject(input)) return input; - var fn, val; - if (PREFERRED_STRING && typeof (fn = input.toString) == 'function' && !isObject(val = fn.call(input))) return val; - if (typeof (fn = input.valueOf) == 'function' && !isObject(val = fn.call(input))) return val; - if (!PREFERRED_STRING && typeof (fn = input.toString) == 'function' && !isObject(val = fn.call(input))) return val; - throw TypeError("Can't convert object to primitive value"); +module.exports = function (input, pref) { + if (!isObject(input) || isSymbol(input)) return input; + var exoticToPrim = input[TO_PRIMITIVE]; + var result; + if (exoticToPrim !== undefined) { + if (pref === undefined) pref = 'default'; + result = exoticToPrim.call(input, pref); + if (!isObject(result) || isSymbol(result)) return result; + throw TypeError("Can't convert object to primitive value"); + } + if (pref === undefined) pref = 'number'; + return ordinaryToPrimitive(input, pref); +}; + + +/***/ }), + +/***/ 4948: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var toPrimitive = __webpack_require__(7593); +var isSymbol = __webpack_require__(2190); + +// `ToPropertyKey` abstract operation +// https://tc39.es/ecma262/#sec-topropertykey +module.exports = function (argument) { + var key = toPrimitive(argument, 'string'); + return isSymbol(key) ? key : String(key); }; @@ -14996,6 +15141,19 @@ test[TO_STRING_TAG] = 'z'; module.exports = String(test) === '[object z]'; +/***/ }), + +/***/ 1340: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +var isSymbol = __webpack_require__(2190); + +module.exports = function (argument) { + if (isSymbol(argument)) throw TypeError('Cannot convert a Symbol value to a string'); + return String(argument); +}; + + /***/ }), /***/ 9711: @@ -15361,6 +15519,7 @@ var redefine = __webpack_require__(1320); var has = __webpack_require__(6656); var classof = __webpack_require__(4326); var inheritIfRequired = __webpack_require__(9587); +var isSymbol = __webpack_require__(2190); var toPrimitive = __webpack_require__(7593); var fails = __webpack_require__(7293); var create = __webpack_require__(30); @@ -15379,7 +15538,8 @@ var BROKEN_CLASSOF = classof(create(NumberPrototype)) == NUMBER; // `ToNumber` abstract operation // https://tc39.es/ecma262/#sec-tonumber var toNumber = function (argument) { - var it = toPrimitive(argument, false); + if (isSymbol(argument)) throw TypeError('Cannot convert a Symbol value to a number'); + var it = toPrimitive(argument, 'number'); var first, third, radix, maxCode, digits, length, index, code; if (typeof it == 'string' && it.length > 2) { it = trim(it); @@ -15636,6 +15796,7 @@ var createNonEnumerableProperty = __webpack_require__(8880); var defineProperty = __webpack_require__(3070).f; var getOwnPropertyNames = __webpack_require__(8006).f; var isRegExp = __webpack_require__(7850); +var toString = __webpack_require__(1340); var getFlags = __webpack_require__(7066); var stickyHelpers = __webpack_require__(2999); var redefine = __webpack_require__(1320); @@ -15755,8 +15916,8 @@ if (isForced('RegExp', BASE_FORCED)) { if (flagsAreUndefined) flags = 'flags' in rawPattern ? rawPattern.flags : getFlags.call(rawPattern); } - pattern = pattern === undefined ? '' : String(pattern); - flags = flags === undefined ? '' : String(flags); + pattern = pattern === undefined ? '' : toString(pattern); + flags = flags === undefined ? '' : toString(flags); rawPattern = pattern; if (UNSUPPORTED_DOT_ALL && 'dotAll' in re1) { @@ -15844,6 +16005,7 @@ $({ target: 'RegExp', proto: true, forced: /./.exec !== exec }, { var redefine = __webpack_require__(1320); var anObject = __webpack_require__(9670); +var $toString = __webpack_require__(1340); var fails = __webpack_require__(7293); var flags = __webpack_require__(7066); @@ -15860,9 +16022,9 @@ var INCORRECT_NAME = nativeToString.name != TO_STRING; if (NOT_GENERIC || INCORRECT_NAME) { redefine(RegExp.prototype, TO_STRING, function toString() { var R = anObject(this); - var p = String(R.source); + var p = $toString(R.source); var rf = R.flags; - var f = String(rf === undefined && R instanceof RegExp && !('flags' in RegExpPrototype) ? flags.call(R) : rf); + var f = $toString(rf === undefined && R instanceof RegExp && !('flags' in RegExpPrototype) ? flags.call(R) : rf); return '/' + p + '/' + f; }, { unsafe: true }); } @@ -15876,6 +16038,7 @@ if (NOT_GENERIC || INCORRECT_NAME) { "use strict"; var charAt = __webpack_require__(8710).charAt; +var toString = __webpack_require__(1340); var InternalStateModule = __webpack_require__(9909); var defineIterator = __webpack_require__(654); @@ -15888,7 +16051,7 @@ var getInternalState = InternalStateModule.getterFor(STRING_ITERATOR); defineIterator(String, 'String', function (iterated) { setInternalState(this, { type: STRING_ITERATOR, - string: String(iterated), + string: toString(iterated), index: 0 }); // `%StringIteratorPrototype%.next` method @@ -15915,6 +16078,7 @@ defineIterator(String, 'String', function (iterated) { var fixRegExpWellKnownSymbolLogic = __webpack_require__(7007); var anObject = __webpack_require__(9670); var toLength = __webpack_require__(7466); +var toString = __webpack_require__(1340); var requireObjectCoercible = __webpack_require__(4488); var advanceStringIndex = __webpack_require__(1530); var regExpExec = __webpack_require__(7651); @@ -15927,16 +16091,16 @@ fixRegExpWellKnownSymbolLogic('match', function (MATCH, nativeMatch, maybeCallNa function match(regexp) { var O = requireObjectCoercible(this); var matcher = regexp == undefined ? undefined : regexp[MATCH]; - return matcher !== undefined ? matcher.call(regexp, O) : new RegExp(regexp)[MATCH](String(O)); + return matcher !== undefined ? matcher.call(regexp, O) : new RegExp(regexp)[MATCH](toString(O)); }, // `RegExp.prototype[@@match]` method // https://tc39.es/ecma262/#sec-regexp.prototype-@@match function (string) { - var res = maybeCallNative(nativeMatch, this, string); - if (res.done) return res.value; - var rx = anObject(this); - var S = String(string); + var S = toString(string); + var res = maybeCallNative(nativeMatch, rx, S); + + if (res.done) return res.value; if (!rx.global) return regExpExec(rx, S); @@ -15946,7 +16110,7 @@ fixRegExpWellKnownSymbolLogic('match', function (MATCH, nativeMatch, maybeCallNa var n = 0; var result; while ((result = regExpExec(rx, S)) !== null) { - var matchStr = String(result[0]); + var matchStr = toString(result[0]); A[n] = matchStr; if (matchStr === '') rx.lastIndex = advanceStringIndex(S, toLength(rx.lastIndex), fullUnicode); n++; @@ -15982,8 +16146,9 @@ $({ target: 'String', proto: true }, { var fixRegExpWellKnownSymbolLogic = __webpack_require__(7007); var fails = __webpack_require__(7293); var anObject = __webpack_require__(9670); -var toLength = __webpack_require__(7466); var toInteger = __webpack_require__(9958); +var toLength = __webpack_require__(7466); +var toString = __webpack_require__(1340); var requireObjectCoercible = __webpack_require__(4488); var advanceStringIndex = __webpack_require__(1530); var getSubstitution = __webpack_require__(647); @@ -16020,6 +16185,7 @@ var REPLACE_SUPPORTS_NAMED_GROUPS = !fails(function () { result.groups = { a: '7' }; return result; }; + // eslint-disable-next-line regexp/no-useless-dollar-replacements -- false positive return ''.replace(re, '$') !== '7'; }); @@ -16035,25 +16201,25 @@ fixRegExpWellKnownSymbolLogic('replace', function (_, nativeReplace, maybeCallNa var replacer = searchValue == undefined ? undefined : searchValue[REPLACE]; return replacer !== undefined ? replacer.call(searchValue, O, replaceValue) - : nativeReplace.call(String(O), searchValue, replaceValue); + : nativeReplace.call(toString(O), searchValue, replaceValue); }, // `RegExp.prototype[@@replace]` method // https://tc39.es/ecma262/#sec-regexp.prototype-@@replace function (string, replaceValue) { + var rx = anObject(this); + var S = toString(string); + if ( typeof replaceValue === 'string' && replaceValue.indexOf(UNSAFE_SUBSTITUTE) === -1 && replaceValue.indexOf('$<') === -1 ) { - var res = maybeCallNative(nativeReplace, this, string, replaceValue); + var res = maybeCallNative(nativeReplace, rx, S, replaceValue); if (res.done) return res.value; } - var rx = anObject(this); - var S = String(string); - var functionalReplace = typeof replaceValue === 'function'; - if (!functionalReplace) replaceValue = String(replaceValue); + if (!functionalReplace) replaceValue = toString(replaceValue); var global = rx.global; if (global) { @@ -16068,7 +16234,7 @@ fixRegExpWellKnownSymbolLogic('replace', function (_, nativeReplace, maybeCallNa results.push(result); if (!global) break; - var matchStr = String(result[0]); + var matchStr = toString(result[0]); if (matchStr === '') rx.lastIndex = advanceStringIndex(S, toLength(rx.lastIndex), fullUnicode); } @@ -16077,7 +16243,7 @@ fixRegExpWellKnownSymbolLogic('replace', function (_, nativeReplace, maybeCallNa for (var i = 0; i < results.length; i++) { result = results[i]; - var matched = String(result[0]); + var matched = toString(result[0]); var position = max(min(toInteger(result.index), S.length), 0); var captures = []; // NOTE: This is equivalent to @@ -16090,7 +16256,7 @@ fixRegExpWellKnownSymbolLogic('replace', function (_, nativeReplace, maybeCallNa if (functionalReplace) { var replacerArgs = [matched].concat(captures, position, S); if (namedCaptures !== undefined) replacerArgs.push(namedCaptures); - var replacement = String(replaceValue.apply(undefined, replacerArgs)); + var replacement = toString(replaceValue.apply(undefined, replacerArgs)); } else { replacement = getSubstitution(matched, S, position, captures, namedCaptures, replaceValue); } @@ -16119,6 +16285,7 @@ var requireObjectCoercible = __webpack_require__(4488); var speciesConstructor = __webpack_require__(6707); var advanceStringIndex = __webpack_require__(1530); var toLength = __webpack_require__(7466); +var toString = __webpack_require__(1340); var callRegExpExec = __webpack_require__(7651); var regexpExec = __webpack_require__(2261); var stickyHelpers = __webpack_require__(2999); @@ -16149,13 +16316,13 @@ fixRegExpWellKnownSymbolLogic('split', function (SPLIT, nativeSplit, maybeCallNa 'test'.split(/(?:)/, -1).length != 4 || 'ab'.split(/(?:ab)*/).length != 2 || '.'.split(/(.?)(.?)/).length != 4 || - // eslint-disable-next-line regexp/no-assertion-capturing-group, regexp/no-empty-group -- required for testing + // eslint-disable-next-line regexp/no-empty-capturing-group, regexp/no-empty-group -- required for testing '.'.split(/()()/).length > 1 || ''.split(/.?/).length ) { // based on es5-shim implementation, need to rework it internalSplit = function (separator, limit) { - var string = String(requireObjectCoercible(this)); + var string = toString(requireObjectCoercible(this)); var lim = limit === undefined ? MAX_UINT32 : limit >>> 0; if (lim === 0) return []; if (separator === undefined) return [string]; @@ -16203,7 +16370,7 @@ fixRegExpWellKnownSymbolLogic('split', function (SPLIT, nativeSplit, maybeCallNa var splitter = separator == undefined ? undefined : separator[SPLIT]; return splitter !== undefined ? splitter.call(separator, O, limit) - : internalSplit.call(String(O), separator, limit); + : internalSplit.call(toString(O), separator, limit); }, // `RegExp.prototype[@@split]` method // https://tc39.es/ecma262/#sec-regexp.prototype-@@split @@ -16211,11 +16378,12 @@ fixRegExpWellKnownSymbolLogic('split', function (SPLIT, nativeSplit, maybeCallNa // NOTE: This cannot be properly polyfilled in engines that don't support // the 'y' flag. function (string, limit) { - var res = maybeCallNative(internalSplit, this, string, limit, internalSplit !== nativeSplit); + var rx = anObject(this); + var S = toString(string); + var res = maybeCallNative(internalSplit, rx, S, limit, internalSplit !== nativeSplit); + if (res.done) return res.value; - var rx = anObject(this); - var S = String(string); var C = speciesConstructor(rx, RegExp); var unicodeMatching = rx.unicode; @@ -16362,15 +16530,16 @@ var getBuiltIn = __webpack_require__(5005); var IS_PURE = __webpack_require__(1913); var DESCRIPTORS = __webpack_require__(9781); var NATIVE_SYMBOL = __webpack_require__(133); -var USE_SYMBOL_AS_UID = __webpack_require__(3307); var fails = __webpack_require__(7293); var has = __webpack_require__(6656); var isArray = __webpack_require__(3157); var isObject = __webpack_require__(111); +var isSymbol = __webpack_require__(2190); var anObject = __webpack_require__(9670); var toObject = __webpack_require__(7908); var toIndexedObject = __webpack_require__(5656); -var toPrimitive = __webpack_require__(7593); +var toPropertyKey = __webpack_require__(4948); +var $toString = __webpack_require__(1340); var createPropertyDescriptor = __webpack_require__(9114); var nativeObjectCreate = __webpack_require__(30); var objectKeys = __webpack_require__(1956); @@ -16440,16 +16609,10 @@ var wrap = function (tag, description) { return symbol; }; -var isSymbol = USE_SYMBOL_AS_UID ? function (it) { - return typeof it == 'symbol'; -} : function (it) { - return Object(it) instanceof $Symbol; -}; - var $defineProperty = function defineProperty(O, P, Attributes) { if (O === ObjectPrototype) $defineProperty(ObjectPrototypeSymbols, P, Attributes); anObject(O); - var key = toPrimitive(P, true); + var key = toPropertyKey(P); anObject(Attributes); if (has(AllSymbols, key)) { if (!Attributes.enumerable) { @@ -16477,7 +16640,7 @@ var $create = function create(O, Properties) { }; var $propertyIsEnumerable = function propertyIsEnumerable(V) { - var P = toPrimitive(V, true); + var P = toPropertyKey(V); var enumerable = nativePropertyIsEnumerable.call(this, P); if (this === ObjectPrototype && has(AllSymbols, P) && !has(ObjectPrototypeSymbols, P)) return false; return enumerable || !has(this, P) || !has(AllSymbols, P) || has(this, HIDDEN) && this[HIDDEN][P] ? enumerable : true; @@ -16485,7 +16648,7 @@ var $propertyIsEnumerable = function propertyIsEnumerable(V) { var $getOwnPropertyDescriptor = function getOwnPropertyDescriptor(O, P) { var it = toIndexedObject(O); - var key = toPrimitive(P, true); + var key = toPropertyKey(P); if (it === ObjectPrototype && has(AllSymbols, key) && !has(ObjectPrototypeSymbols, key)) return; var descriptor = nativeGetOwnPropertyDescriptor(it, key); if (descriptor && has(AllSymbols, key) && !(has(it, HIDDEN) && it[HIDDEN][key])) { @@ -16520,7 +16683,7 @@ var $getOwnPropertySymbols = function getOwnPropertySymbols(O) { if (!NATIVE_SYMBOL) { $Symbol = function Symbol() { if (this instanceof $Symbol) throw TypeError('Symbol is not a constructor'); - var description = !arguments.length || arguments[0] === undefined ? undefined : String(arguments[0]); + var description = !arguments.length || arguments[0] === undefined ? undefined : $toString(arguments[0]); var tag = uid(description); var setter = function (value) { if (this === ObjectPrototype) setter.call(ObjectPrototypeSymbols, value); @@ -16575,7 +16738,7 @@ $({ target: SYMBOL, stat: true, forced: !NATIVE_SYMBOL }, { // `Symbol.for` method // https://tc39.es/ecma262/#sec-symbol.for 'for': function (key) { - var string = String(key); + var string = $toString(key); if (has(StringToSymbolRegistry, string)) return StringToSymbolRegistry[string]; var symbol = $Symbol(string); StringToSymbolRegistry[string] = symbol; @@ -16735,7 +16898,7 @@ for (var COLLECTION_NAME in DOMIterables) { /***/ 7856: /***/ (function(module) { -/*! @license DOMPurify | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.2.2/LICENSE */ +/*! @license DOMPurify 2.3.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.1/LICENSE */ (function (global, factory) { true ? module.exports = factory() : @@ -16985,7 +17148,7 @@ for (var COLLECTION_NAME in DOMIterables) { * Version label, exposed for easier checks * if DOMPurify is up to date or not */ - DOMPurify.version = '2.2.9'; + DOMPurify.version = '2.3.1'; /** * Array of elements that DOMPurify removed during sanitation. @@ -17043,7 +17206,8 @@ for (var COLLECTION_NAME in DOMIterables) { var _document = document, implementation = _document.implementation, createNodeIterator = _document.createNodeIterator, - createDocumentFragment = _document.createDocumentFragment; + createDocumentFragment = _document.createDocumentFragment, + getElementsByTagName = _document.getElementsByTagName; var importNode = originalDocument.importNode; @@ -17150,7 +17314,8 @@ for (var COLLECTION_NAME in DOMIterables) { var USE_PROFILES = {}; /* Tags to ignore content of when KEEP_CONTENT is true */ - var FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']); + var FORBID_CONTENTS = null; + var DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']); /* Tags that are safe for data: URIs */ var DATA_URI_TAGS = null; @@ -17158,7 +17323,7 @@ for (var COLLECTION_NAME in DOMIterables) { /* Attributes safe for values like "javascript:" */ var URI_SAFE_ATTRIBUTES = null; - var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'summary', 'title', 'value', 'style', 'xmlns']); + var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']); var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML'; var SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; @@ -17199,6 +17364,7 @@ for (var COLLECTION_NAME in DOMIterables) { ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR; URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR) : DEFAULT_URI_SAFE_ATTRIBUTES; DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS) : DEFAULT_DATA_URI_TAGS; + FORBID_CONTENTS = 'FORBID_CONTENTS' in cfg ? addToSet({}, cfg.FORBID_CONTENTS) : DEFAULT_FORBID_CONTENTS; FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS) : {}; FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR) : {}; USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false; @@ -17274,6 +17440,14 @@ for (var COLLECTION_NAME in DOMIterables) { addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR); } + if (cfg.FORBID_CONTENTS) { + if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) { + FORBID_CONTENTS = clone(FORBID_CONTENTS); + } + + addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS); + } + /* Add #text in case KEEP_CONTENT is set to true */ if (KEEP_CONTENT) { ALLOWED_TAGS['#text'] = true; @@ -17505,6 +17679,10 @@ for (var COLLECTION_NAME in DOMIterables) { } /* Work on whole document or just its body */ + if (NAMESPACE === HTML_NAMESPACE) { + return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0]; + } + return WHOLE_DOCUMENT ? doc.documentElement : body; }; @@ -17607,6 +17785,12 @@ for (var COLLECTION_NAME in DOMIterables) { return true; } + /* Mitigate a problem with templates inside select */ + if (tagName === 'select' && regExpTest(/