a3dbceed952bbbfbd67e73148714bb3a828a0ec8 chmalee Mon Sep 21 15:24:52 2026 -0700 Add some uiTests for hubspace, mostly checking that file metadata editing works when a hub.txt is in a batch. This test does not actually test uploading anything, refs #38398 diff --git src/hg/hgHubConnect/tests/pages/hgHubConnect.js src/hg/hgHubConnect/tests/pages/hgHubConnect.js new file mode 100644 index 00000000000..99a8ef6b03b --- /dev/null +++ src/hg/hgHubConnect/tests/pages/hgHubConnect.js @@ -0,0 +1,130 @@ +// The hgHubConnect page object: the Hub Upload tab and the Uppy dashboard it +// opens. +// +// Verbs do a thing and wait for their own completion condition. Readers return +// data and never assert. There are two sources of truth on this page and they +// can disagree: what the dashboard draws, and what uppy holds in its own file +// list. Both have readers here, because the list is what gets sent. + +'use strict'; + +const site = require('../../../utils/uiTest/lib/site'); +const wait = require('../../../utils/uiTest/lib/wait'); + +// The modal panel. The .uppy-Dashboard wrapper around it is a zero-size div +// that exists whether the modal is open or not. +const DASHBOARD = '.uppy-Dashboard-inner'; +const FILE_CARD = '.uppy-Dashboard-FileCard'; +// The card's two buttons: a primary "Save changes" that writes the card back +// over file.meta, and a link-styled "Cancel" that does not. +const SAVE_CARD = '.uppy-Dashboard-FileCard .uppy-c-btn-primary'; +const HUB_NAME_INPUT = '#uppy-Dashboard-FileCard-input-parentDir'; + +// ---------------------------------------------------------------- verbs + +async function openUploadTab(p, env) { + // hgHubConnect.js picks the tab from the URL hash on load, and hubCreate's + // init() asks for the file list only when Hub Upload is the active tab. + await site.goto(p, site.url(env, 'hgHubConnect') + '#hubUpload'); + await p.waitForSelector('#hubUpload'); + await wait.noPending(p); +} + +async function openDashboard(p) { + // The Upload button is a DataTables button, disabled until the file list + // has come back, which only happens for a logged-in user. + await wait.until(p, () => { + const b = document.querySelector('.uploadButton'); + return !!b && !b.classList.contains('disabled'); + }, 'the Upload button never became clickable'); + await p.click('.uploadButton'); + await p.waitForSelector(DASHBOARD); +} + +async function addFile(p, name, text) { + // The Dashboard renders two hidden file inputs, one of them carrying + // webkitdirectory for a folder drop. Pick the plain one. + const handle = await p.evaluateHandle(() => + [...document.querySelectorAll('input.uppy-Dashboard-input')] + .find(i => i.type === 'file' && !i.webkitdirectory)); + const input = handle.asElement(); + if (!input) { + throw new Error('the Uppy dashboard has no file input to drop a file through'); + } + await input.setInputFiles({ + name: name, + mimeType: 'text/plain', + buffer: Buffer.from(text), + }); + const fail = await wait.eventually(async () => { + const f = await fileNamed(p, name); + return f ? null : `uppy never took ${name}`; + }, `uppy never took ${name}`); + if (fail) { + throw new Error(fail); + } +} + +async function waitForFileCard(p) { + await p.waitForSelector(FILE_CARD); +} + +async function saveFileCard(p) { + // "Save changes", which is what a user clicks to get back to the upload + // list. It writes the card's own copy of the metadata back over file.meta. + await p.waitForSelector(FILE_CARD); + await p.click(SAVE_CARD); + await p.waitForSelector(FILE_CARD, { state: 'detached' }); +} + +// ---------------------------------------------------------------- readers + +async function hasUploadTab(p) { + return p.evaluate(() => + !!document.querySelector('#tabs > ul > li > a[href="#hubUpload"]')); +} + +async function files(p) { + // uppy's own list, which is what will be sent. + const out = await site.pageGlobal(p, + 'uppy.getFiles().map(f => ({name: f.name, meta: f.meta}))'); + return out || []; +} + +async function fileNamed(p, name) { + return (await files(p)).find(f => f.name === name) || null; +} + +async function metaOf(p, name) { + const f = await fileNamed(p, name); + return f ? f.meta : null; +} + +async function cardGenome(p) { + // The card shows the genome as a locked text box when a hub.txt or a 2bit + // decided it, and as the assembly picker otherwise. + return p.evaluate(() => { + const card = document.querySelector('.uppy-Dashboard-FileCard'); + if (!card) { + return null; + } + const locked = card.querySelector('input[id$="AsmHubInput"]'); + if (locked) { + return locked.value; + } + const picker = card.querySelector('select[id$="DbSelect"]'); + return picker ? picker.value : null; + }); +} + +async function cardHubName(p) { + return p.evaluate((sel) => { + const el = document.querySelector(sel); + return el ? el.value : null; + }, HUB_NAME_INPUT); +} + +module.exports = { + openUploadTab, openDashboard, addFile, waitForFileCard, saveFileCard, + hasUploadTab, files, fileNamed, metaOf, cardGenome, cardHubName, +};