107 lines
7.7 KiB
JavaScript
107 lines
7.7 KiB
JavaScript
const { chromium } = require('playwright');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const assert = require('node:assert/strict');
|
|
const repo = path.resolve(__dirname, '../..');
|
|
(async () => {
|
|
const browser = await chromium.launch({ executablePath: process.env.CHROMIUM_PATH });
|
|
try {
|
|
const page = await browser.newPage();
|
|
const errors = [];
|
|
page.on('pageerror', error => errors.push(error.message));
|
|
await page.route('https://plotdirector.test/**', route => route.fulfill({ contentType: 'text/html', body: '<html><body></body></html>' }));
|
|
await page.goto('https://plotdirector.test/Locations');
|
|
const layout = fs.readFileSync(path.join(repo, 'PlotLine/Views/Shared/_Layout.cshtml'), 'utf8');
|
|
const modalStart = layout.indexOf(' <div class="modal fade" id="plotlineConfirmModal"');
|
|
const modal = layout.slice(modalStart, layout.indexOf(' <script', modalStart));
|
|
const rows = [ ['1','Room','',''], ['2','Building','',''], ['3','Cupboard','1','Room'], ['4','Annex','2','Building'] ]
|
|
.map(([id,name,parent,parentName]) => `<div style="padding:24px;border:1px solid" draggable="true" data-drag-type="location" data-drag-id="${id}" data-drag-label="${name}" data-parent-location-id="${parent}" data-parent-location-name="${parentName}"><a href="/Locations/Details/${id}">${name}</a></div>`).join('');
|
|
await page.setContent(`<select data-location-drag-action><option value="parent">Change parent</option><option value="relationship">Create relationship</option></select>
|
|
<p data-location-move-error></p><p data-location-move-status></p>
|
|
<form action="/Locations/Reparent" method="post" data-location-reparent-form data-confirm-message="Change parent?" data-confirm-button-text="Move location" data-confirm-button-class="btn-primary">
|
|
<input type="hidden" name="projectId" value="1"><input type="hidden" name="locationId"><input type="hidden" name="parentLocationId"><input type="hidden" name="expectedParentLocationId">
|
|
</form>
|
|
<div class="modal fade" tabindex="-1" data-location-relationship-modal><div class="modal-dialog"><section class="modal-content d-none" data-drag-drop-panel><h2 data-drag-panel-title></h2><span data-drag-panel-kicker></span><p data-drag-panel-summary></p>
|
|
<p data-location-relationship-error></p><form action="/Locations/DropRelationship" data-drag-form="location-relationship"><input name="FromLocationID"><input name="ToLocationID"><button type="submit">Save relationship</button></form></section></div></div><div style="height:900px"></div><section data-location-tree>${rows}</section><div style="height:2000px"></div>${modal}`);
|
|
await page.addStyleTag({ path: path.join(repo,'PlotLine/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css') });
|
|
await page.addScriptTag({ path: path.join(repo,'PlotLine/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js') });
|
|
// Load the entire production script: an earlier initialization error can disable dragging.
|
|
await page.addScriptTag({ path: path.join(repo,'PlotLine/wwwroot/js/site.js') });
|
|
await page.evaluate(() => {
|
|
window.savedMoves = [];
|
|
window.relationshipSaves = 0;
|
|
window.failNextSave = false;
|
|
window.fetch = async (url, options) => {
|
|
if (window.failNextSave) {
|
|
window.failNextSave = false;
|
|
return new Response(JSON.stringify({error:'The parent has changed. Review the tree.'}), {status:400});
|
|
}
|
|
if (url.endsWith('/DropRelationship')) {
|
|
window.relationshipSaves++;
|
|
return new Response(JSON.stringify({message:'Saved'}));
|
|
}
|
|
const values = Object.fromEntries(options.body);
|
|
window.savedMoves.push(values);
|
|
const tree = document.querySelector('[data-location-tree]').cloneNode(true);
|
|
const source = tree.querySelector(`[data-drag-id="${values.locationId}"]`);
|
|
const target = tree.querySelector(`[data-drag-id="${values.parentLocationId}"]`);
|
|
source.dataset.parentLocationId = values.parentLocationId;
|
|
source.dataset.parentLocationName = target.dataset.dragLabel;
|
|
tree.append(source);
|
|
return new Response(tree.outerHTML, {headers:{'Content-Type':'text/html'}});
|
|
};
|
|
});
|
|
const drag = async (source, target) => page.locator(`[data-drag-id="${source}"]`).dragTo(page.locator(`[data-drag-id="${target}"]`));
|
|
const confirm = page.locator('#plotlineConfirmModal');
|
|
await drag(1,2);
|
|
await confirm.waitFor({state:'visible'});
|
|
assert.match(await confirm.innerText(), /from top level to “Building”/);
|
|
assert.equal(await page.evaluate(() => savedMoves.length),0);
|
|
await confirm.locator('[data-bs-dismiss="modal"]').last().click();
|
|
await confirm.waitFor({state:'hidden'});
|
|
await page.locator('.modal-backdrop').waitFor({state:'hidden'});
|
|
assert.equal(await page.evaluate(() => savedMoves.length),0);
|
|
await drag(1,2);
|
|
const scrollBeforeSave = await page.evaluate(() => window.scrollY);
|
|
await confirm.locator('[data-confirm-submit]').click();
|
|
await confirm.waitFor({state:'hidden'});
|
|
await page.locator('.modal-backdrop').waitFor({state:'hidden'});
|
|
await page.waitForFunction(() => document.querySelector('[data-location-move-status]').textContent.includes('moved'));
|
|
assert.ok(Math.abs(await page.evaluate(() => window.scrollY) - scrollBeforeSave) < 2, 'AJAX update must preserve scroll');
|
|
assert.deepEqual(await page.evaluate(() => savedMoves[0]), {projectId:'1',locationId:'1',parentLocationId:'2',expectedParentLocationId:''});
|
|
await drag(4,1);
|
|
await confirm.waitFor({state:'visible'});
|
|
assert.match(await confirm.innerText(), /from Building to “Room”/);
|
|
await confirm.locator('[data-confirm-submit]').click();
|
|
await confirm.waitFor({state:'hidden'});
|
|
await page.locator('.modal-backdrop').waitFor({state:'hidden'});
|
|
assert.equal(await page.evaluate(() => savedMoves[1].expectedParentLocationId),'2');
|
|
for (const [source,target] of [[1,1],[1,3],[1,2]]) {
|
|
await drag(source,target);
|
|
assert.equal(await confirm.isVisible(),false);
|
|
assert.equal(await page.evaluate(() => savedMoves.length),2);
|
|
}
|
|
// Server validation failures leave the existing tree intact and explain the error.
|
|
await page.evaluate(() => window.failNextSave = true);
|
|
await drag(4,2);
|
|
await confirm.locator('[data-confirm-submit]').click();
|
|
await confirm.waitFor({state:'hidden'});
|
|
await page.locator('.modal-backdrop').waitFor({state:'hidden'});
|
|
assert.match(await page.locator('[data-location-move-error]').innerText(), /parent has changed/);
|
|
assert.equal(await page.locator('[data-drag-id="4"]').getAttribute('data-parent-location-id'),'1');
|
|
await page.selectOption('[data-location-drag-action]','relationship');
|
|
await drag(1,2);
|
|
await page.locator('[data-drag-drop-panel]').waitFor({state:'visible'});
|
|
assert.equal(await page.locator('[name="FromLocationID"]').inputValue(),'1');
|
|
assert.equal(await page.locator('[name="ToLocationID"]').inputValue(),'2');
|
|
const relationshipScroll = await page.evaluate(() => window.scrollY);
|
|
await page.locator('[data-drag-form="location-relationship"] button').click();
|
|
await page.locator('[data-location-relationship-modal]').waitFor({state:'hidden'});
|
|
await page.locator('.modal-backdrop').waitFor({state:'hidden'});
|
|
assert.ok(Math.abs(await page.evaluate(() => window.scrollY) - relationshipScroll) < 2);
|
|
assert.equal(await page.evaluate(() => relationshipSaves),1);
|
|
assert.deepEqual(errors,[]);
|
|
console.log('PASS: drag assignment/reassignment, confirmation, cancellation, invalid targets, AJAX refresh, scroll preservation, server errors, relationship modal/AJAX, full site.js initialization');
|
|
} finally { await browser.close(); }
|
|
})().catch(error => { console.error(error); process.exitCode = 1; });
|