import fs from 'fs';
import path from 'path';
/*
* The map pin table's action cells.
*
* Each row offers edit, add-to-playlist and delete, and they were laid out as bare siblings in
* a table cell. That makes them inline-block boxes aligned on their text BASELINES, and a
* Button's baseline is not an ActionIcon's -- measured on the running app, the middle control
* sat 4.3px above the other two -- and they touched, because JSX leaves no whitespace between
* elements. `Group` is what every other row of buttons in the app uses.
*
* Checked in the source because neither row is reachable from a test that could measure it:
* `MapPins` fetches its pins from the API and builds its rows through SortableTable, and the
* row builders are internal to the module. ui-layout.cy.js measures the pattern itself --
* that bare siblings really are misaligned and that a Group really does fix it -- so what is
* left to guard is that these cells use one.
*/
const source = fs.readFileSync(path.join(__dirname, 'Map.js'), 'utf8');
/** The contents of every `
` in the file. */
const cells = () => {
const found = [];
const open = '', at);
if (end !== -1) found.push(source.slice(at, end));
}
return found;
};
describe('the map pin action cells', () => {
it('finds the cells at all', () => {
// The premise. A parser that matched nothing would report no offenders and read
// exactly like a file that was already correct.
expect(cells().length).toBeGreaterThan(5);
});
/*
* Named cells, not a count of control tags.
*
* Counting was tried first and does not work: a cell whose buttons are the two arms of a
* ternary renders exactly one of them and needs no Group -- the "Built" / "Build" cell on
* the map files table is that shape -- and excluding ternaries to allow for it also
* excluded the subscribe cell, whose `role={isSubscribed ? ... : ...}` has nothing to do
* with how many controls it holds. A guard that quietly stops covering a cell is worse
* than one with a stated scope.
*
* So each cell is named by something only it contains, and the general claim -- that bare
* inline controls really are misaligned and that a Group really does fix it -- is measured
* in ui-layout.cy.js instead.
*/
const rows = [
{name: 'pin actions', marker: 'setEditingId(pin.id)'},
/*
* `role='cancel'`, not `onSave(...)`: the edit row's TextInput calls onSave from its
* own onKeyDown, in a different cell, so that marker matched the input's cell and this
* quietly checked the wrong one. A marker has to be unique to the cell it names.
*/
{name: 'pin edit save/cancel', marker: "role='cancel'"},
{name: 'map file subscribe', marker: 'setPreviewOpen(true)'},
];
rows.forEach(({name, marker}) => {
it(`groups the ${name} cell`, () => {
const cell = cells().find(candidate => candidate.includes(marker));
/*
* The premise and the claim in one object, because jest's `expect` takes no
* message argument -- that is chai, and passing one throws rather than annotating.
* A rename of the marker would otherwise turn this into a test of nothing.
*/
/*
* The Group must CONTAIN the cell's controls, not merely appear in it. Asserting
* that ` {
const from = text.indexOf(open);
if (from === -1) return '';
const to = text.indexOf(close, from);
// An unclosed Group would otherwise swallow the rest of the cell, which is how
// the first version of this let an EMPTY Group pass with the controls left
// outside it.
return to === -1 ? '' : text.slice(from, to);
};
const inGroup = cell === undefined ? '' : between(cell, '');
const controls = (text) =>
(text.match(/<(?:API)?Button\b| {
/*
* An icon-only Button carries no text, so without `aria-label` it reaches assistive tech
* as an unnamed button. `IconButton` requires a label by construction and
* AddToPlaylistButton supplies one; the plain Button and APIButton used for edit, delete
* and the catalog preview did not, and this change rewrote all three rows.
*
* In the source, for the same reason as the Group guard: these rows are built inside the
* module and fetched from the API.
*/
const openingTag = (text, at) => {
let depth = 0;
for (let i = at; i < text.length; i++) {
const c = text[i];
if (c === '{' || c === '(') depth += 1;
else if (c === '}' || c === ')') depth -= 1;
else if (c === '>' && depth === 0) return {tag: text.slice(at, i + 1), end: i};
}
return {tag: '', end: at};
};
/** Every `