Refactor tests to use single source of truth ASCII diagram (#164)
- Less test bulk - Single source of truth: there is no mismatch between the comment and the expectations (we already caught a few mistakes in the conversion thanks to this benefit) - Easier to maintain and update
This commit is contained in:
parent
954b22995a
commit
57d2cb3dd3
|
@ -6,11 +6,9 @@ const escapeStringRegexp = require('escape-string-regexp');
|
|||
const config = require('../lib/config');
|
||||
const basePath = config.get('basePath');
|
||||
assert(basePath);
|
||||
|
||||
const VALID_SIGIL_TO_ENTITY_DESCRIPTOR_MAP = {
|
||||
'#': 'r',
|
||||
'!': 'roomid',
|
||||
};
|
||||
const {
|
||||
VALID_SIGIL_TO_ENTITY_DESCRIPTOR_MAP,
|
||||
} = require('matrix-public-archive-shared/lib/reference-values');
|
||||
|
||||
// Create a regex string that will match a normal string or the URI encoded string or
|
||||
// any combination of some characters being URI encoded. Only worries about characters
|
||||
|
|
|
@ -23,8 +23,14 @@ const DIRECTION = {
|
|||
backward: 'b',
|
||||
};
|
||||
|
||||
const VALID_SIGIL_TO_ENTITY_DESCRIPTOR_MAP = {
|
||||
'#': 'r',
|
||||
'!': 'roomid',
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
MS_LOOKUP,
|
||||
TIME_PRECISION_VALUES,
|
||||
DIRECTION,
|
||||
VALID_SIGIL_TO_ENTITY_DESCRIPTOR_MAP,
|
||||
};
|
||||
|
|
|
@ -7,7 +7,9 @@ const { TIME_PRECISION_VALUES } = require('matrix-public-archive-shared/lib/refe
|
|||
|
||||
function qsToUrlPiece(qs) {
|
||||
if (qs.toString()) {
|
||||
return `?${qs.toString()}`;
|
||||
// We allow `$` to be unencoded in the query string because it's a valid character
|
||||
// in a Matrix event ID
|
||||
return `?${qs.toString().replace(/%24/g, '$')}`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
|
|
1000
test/e2e-tests.js
1000
test/e2e-tests.js
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,33 @@
|
|||
'use strict';
|
||||
|
||||
const {
|
||||
VALID_SIGIL_TO_ENTITY_DESCRIPTOR_MAP,
|
||||
} = require('matrix-public-archive-shared/lib/reference-values');
|
||||
|
||||
// http://archive.matrix.org/r/some-room:matrix.org/date/2022/11/16T23:59:59?at=$xxx
|
||||
function parseArchiveUrlForRoom(archiveUrlForRoom) {
|
||||
const urlObj = new URL(archiveUrlForRoom);
|
||||
const urlPathname = urlObj.pathname;
|
||||
|
||||
// eslint-disable-next-line no-unused-vars -- It's more clear to leave `match` so we can see what we're destructuring from
|
||||
const [match, roomIdOrAliasDescriptor, roomIdOrAliasUrlPart, urlDateTime] = urlPathname.match(
|
||||
/\/(r|roomid)\/(.*?)\/date\/(.*)/
|
||||
);
|
||||
|
||||
const [sigil] = Object.entries(VALID_SIGIL_TO_ENTITY_DESCRIPTOR_MAP).find(
|
||||
// eslint-disable-next-line no-unused-vars -- It's more clear to leave `sigil` so we can see what we're destructuring from
|
||||
([sigil, entityDescriptor]) => roomIdOrAliasDescriptor === entityDescriptor
|
||||
);
|
||||
const roomIdOrAlias = `${sigil}${roomIdOrAliasUrlPart}`;
|
||||
|
||||
const continueAtEvent = urlObj.searchParams.get('at');
|
||||
|
||||
return {
|
||||
roomIdOrAliasUrlPart,
|
||||
roomIdOrAlias,
|
||||
urlDateTime,
|
||||
continueAtEvent,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = parseArchiveUrlForRoom;
|
|
@ -0,0 +1,211 @@
|
|||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
function assertSequentialNumbersStartingFromOne(numbers) {
|
||||
for (let i = 0; i < numbers.length - 1; i++) {
|
||||
assert.equal(
|
||||
numbers[i],
|
||||
i,
|
||||
`Expected numbers to be sequential starting from 1 but saw ${numbers[i]} at index ${i} from ${numbers}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function findEventRangeFromBracketPairsInLine({ inputLine, regexPattern, eventLine }) {
|
||||
assert(inputLine);
|
||||
assert(regexPattern);
|
||||
assert(eventLine);
|
||||
|
||||
const stringIndiciceToEventMap = new Map();
|
||||
eventLine.replace(/\d+/g, (match, offset /*, string, groups*/) => {
|
||||
const eventNumber = match;
|
||||
stringIndiciceToEventMap.set(offset, parseInt(eventNumber, 10));
|
||||
});
|
||||
assert(stringIndiciceToEventMap.size > 0, `Expected to find at least one event in ${eventLine}`);
|
||||
// Ensure the person defined the events in order
|
||||
assertSequentialNumbersStartingFromOne(stringIndiciceToEventMap.values());
|
||||
|
||||
// Numbers can be multiple digits long. In order to lookup the eventNumber by the
|
||||
// position of the closing bracket, we need to construct a map from the string index
|
||||
// at the end of given eventNumber to the eventNumber.
|
||||
//
|
||||
// ex.
|
||||
// ... <-- 8 <-- 9 <-- 10 <-- 11 <-- 12
|
||||
// [day1 ]
|
||||
const stringIndiceNumberEndToEventMap = new Map();
|
||||
Array.from(stringIndiciceToEventMap.entries()).forEach(([stringIndiceNumber, eventNumber]) => {
|
||||
stringIndiceNumberEndToEventMap.set(
|
||||
stringIndiceNumber + String(eventNumber).length,
|
||||
eventNumber
|
||||
);
|
||||
});
|
||||
|
||||
const matchMap = new Map();
|
||||
inputLine.replace(regexPattern, (match, numberLabel, offset /*, string, groups*/) => {
|
||||
const startEventIndice = offset;
|
||||
const endEventIndice = offset + match.length;
|
||||
|
||||
const startEventNumber = stringIndiciceToEventMap.get(startEventIndice);
|
||||
const endEventNumber = stringIndiceNumberEndToEventMap.get(endEventIndice);
|
||||
assert(
|
||||
startEventNumber,
|
||||
`For match ${numberLabel}, the opening bracket does not line up exactly with an event in the eventLine:\n` +
|
||||
`${eventLine}\n` +
|
||||
`${inputLine}\n` +
|
||||
`Looking for event at startEventIndice=${startEventIndice} in ${JSON.stringify(
|
||||
Object.fromEntries(stringIndiciceToEventMap.entries())
|
||||
)}`
|
||||
);
|
||||
assert(
|
||||
endEventNumber,
|
||||
`For match ${numberLabel}, the closing bracket does not line up exactly with an event in the eventLine:\n` +
|
||||
`${eventLine}\n` +
|
||||
`${inputLine}\n` +
|
||||
`Looking for event at endEventIndice=${endEventIndice} in ${JSON.stringify(
|
||||
Object.fromEntries(stringIndiceNumberEndToEventMap.entries())
|
||||
)}`
|
||||
);
|
||||
assert(
|
||||
endEventNumber > startEventNumber,
|
||||
`For match ${numberLabel}, expected endEventNumber=${endEventNumber} to be greater than startEventNumber=${startEventNumber}`
|
||||
);
|
||||
|
||||
matchMap.set(numberLabel, {
|
||||
startEventNumber,
|
||||
endEventNumber,
|
||||
});
|
||||
});
|
||||
|
||||
return matchMap;
|
||||
}
|
||||
|
||||
// Used in tests to parse a string that defines the structure of a room and the events
|
||||
// in that room.
|
||||
//
|
||||
// ```
|
||||
// const EXAMPLE_ROOM_DAY_MESSAGE_STRUCTURE_STRING = `
|
||||
// [room1 ] [room2 ]
|
||||
// 1 <-- 2 <-- 3 <-- 4 <-- 5 <-- 6 <-- 7 <-- 8 <-- 9 <-- 10 <-- 11 <-- 12
|
||||
// [day1 ] [day2 ]
|
||||
// [page1 ]
|
||||
// |--jump-fwd-4-messages-->|
|
||||
// [page2 ]
|
||||
// `;
|
||||
// ```
|
||||
function parseRoomDayMessageStructure(roomDayMessageStructureString) {
|
||||
assert(roomDayMessageStructureString && roomDayMessageStructureString.length > 0);
|
||||
|
||||
// Strip the leading whitespace from each line
|
||||
const rawLines = roomDayMessageStructureString.split(/\r?\n/);
|
||||
// We choose the second line because the first line is likely to be empty
|
||||
const numWhiteSpaceToStripFromEachLine = rawLines[1].match(/^\s*/)[0].length;
|
||||
const lines = rawLines
|
||||
.map((line) => line.slice(numWhiteSpaceToStripFromEachLine))
|
||||
.filter((line) => {
|
||||
return line.length > 0;
|
||||
});
|
||||
const roomLine = lines[0];
|
||||
const eventLine = lines[1];
|
||||
const dayLine = lines[2];
|
||||
const pageLines = lines.filter((line) => line.match(/\[page\d+\s*\]/));
|
||||
|
||||
const dayToEventRangeMap = findEventRangeFromBracketPairsInLine({
|
||||
inputLine: dayLine,
|
||||
regexPattern: /\[day(\d+)\s*\]/g,
|
||||
eventLine,
|
||||
});
|
||||
// Ensure the person defined the days in order
|
||||
assertSequentialNumbersStartingFromOne(dayToEventRangeMap.keys());
|
||||
// Make a map so it's easier to lookup which day an event is in
|
||||
const eventToDayMap = new Map();
|
||||
dayToEventRangeMap.forEach(({ startEventNumber, endEventNumber }, dayNumber) => {
|
||||
for (let eventNumber = startEventNumber; eventNumber <= endEventNumber; eventNumber++) {
|
||||
eventToDayMap.set(eventNumber, dayNumber);
|
||||
}
|
||||
});
|
||||
|
||||
const roomToEventRangeMap = findEventRangeFromBracketPairsInLine({
|
||||
inputLine: roomLine,
|
||||
regexPattern: /\[room(\d+)\s*\]/g,
|
||||
eventLine,
|
||||
});
|
||||
// Ensure the person defined the rooms in order
|
||||
assertSequentialNumbersStartingFromOne(roomToEventRangeMap.keys());
|
||||
|
||||
function getEventMetaFromEventNumber(eventNumber) {
|
||||
const dayNumber = eventToDayMap.get(eventNumber);
|
||||
assert(
|
||||
dayNumber,
|
||||
`Could not find event${eventNumber} associated with any day (check the brackets for "[dayX ]" to make sure it encompasses that event)\n` +
|
||||
`${eventLine}\n` +
|
||||
`${dayLine}\n` +
|
||||
`eventToDayMap=${JSON.stringify(Object.fromEntries(eventToDayMap.entries()))}`
|
||||
);
|
||||
const eventRangeInDay = dayToEventRangeMap.get(dayNumber);
|
||||
const event = {
|
||||
eventNumber,
|
||||
eventIndexInDay: eventNumber - eventRangeInDay.startEventNumber,
|
||||
dayNumber,
|
||||
};
|
||||
return event;
|
||||
}
|
||||
|
||||
// Get a list of events that should be in each room
|
||||
const rooms = Array.from(roomToEventRangeMap.keys()).map((roomNumber) => {
|
||||
const { startEventNumber, endEventNumber } = roomToEventRangeMap.get(roomNumber);
|
||||
const events = [];
|
||||
for (let eventNumber = startEventNumber; eventNumber <= endEventNumber; eventNumber++) {
|
||||
events.push(getEventMetaFromEventNumber(eventNumber));
|
||||
}
|
||||
|
||||
return {
|
||||
events,
|
||||
};
|
||||
});
|
||||
|
||||
// Get a list of events that should be displayed on each page
|
||||
const pages = pageLines.map((pageLine, index) => {
|
||||
const pageToEventRangeMap = findEventRangeFromBracketPairsInLine({
|
||||
inputLine: pageLine,
|
||||
regexPattern: /\[page(\d+)\s*\]/g,
|
||||
eventLine,
|
||||
});
|
||||
assert(
|
||||
pageToEventRangeMap.size === 1,
|
||||
`Expected to find exactly one page in line "${pageLine}" (found ${pageToEventRangeMap.size}). ` +
|
||||
`Because pages can overlap on the events they display, they should be on their own lines`
|
||||
);
|
||||
|
||||
// Ensure the person defined the pages in order
|
||||
assert(Array.from(pageToEventRangeMap.keys())[0], index + 1);
|
||||
|
||||
const { startEventNumber, endEventNumber } = Array.from(pageToEventRangeMap.values())[0];
|
||||
|
||||
const events = [];
|
||||
for (let eventNumber = startEventNumber; eventNumber <= endEventNumber; eventNumber++) {
|
||||
events.push(getEventMetaFromEventNumber(eventNumber));
|
||||
}
|
||||
|
||||
return {
|
||||
events,
|
||||
};
|
||||
});
|
||||
// Ensure that each page has the same number of events on it
|
||||
const numEventsOnEachPage = pages.map((page) => page.events.length);
|
||||
// The page limit is X but each page will display X + 1 messages because we fetch one
|
||||
// extra to determine overflow.
|
||||
const archiveMessageLimit = numEventsOnEachPage[0] - 1;
|
||||
assert(
|
||||
numEventsOnEachPage.every((numEvents) => numEvents === archiveMessageLimit + 1),
|
||||
`Expected all pages to have the same number of events (archiveMessageLimit + 1) where archiveMessageLimit=${archiveMessageLimit} but found ${numEventsOnEachPage}`
|
||||
);
|
||||
|
||||
return {
|
||||
rooms,
|
||||
archiveMessageLimit,
|
||||
pages,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = parseRoomDayMessageStructure;
|
Loading…
Reference in New Issue