Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b825442
spec(content-drive): Title / All Content scope selector for the searc…
KevinDavilaDotCMS Sep 11, 2026
77abd40
spec(content-drive): address review round 1 — All Fields, searchScope…
KevinDavilaDotCMS Sep 14, 2026
1ce8cdd
spec(content-drive): fold #37532 into the spec — literal-text search …
KevinDavilaDotCMS Sep 14, 2026
012f5d8
fix(content-drive): match search terms as literal text, not query syntax
KevinDavilaDotCMS Sep 14, 2026
6a90973
fix(content-drive): show a failed search as an error, not an empty grid
KevinDavilaDotCMS Sep 14, 2026
31ed88e
feat(content-drive): add the Title search scope to the drive search
KevinDavilaDotCMS Sep 14, 2026
470ac34
feat(content-drive): add the Title / All Fields control to the search…
KevinDavilaDotCMS Sep 15, 2026
25f4260
fix(content-drive): register the search scope with the URL decoder
KevinDavilaDotCMS Sep 15, 2026
ebc8f37
fix(content-drive): stop the Title scope leaking into every field
KevinDavilaDotCMS Sep 15, 2026
0afca0d
fix(content-drive): strip query syntax in Title scope instead of esca…
KevinDavilaDotCMS Sep 15, 2026
61f1674
test(content-drive): pin the search scope surviving reload and Back
KevinDavilaDotCMS Sep 15, 2026
6c0aa1a
refactor(content-drive): make the scope control the dropdown the tick…
KevinDavilaDotCMS Sep 15, 2026
a00fb2a
refactor(content-drive): join the scope control to the input with p-i…
KevinDavilaDotCMS Sep 15, 2026
4001353
fix(content-drive): pin the scope control's width and match the chip …
KevinDavilaDotCMS Sep 15, 2026
40726d9
refactor(content-drive): bring the scope control in line with the fro…
KevinDavilaDotCMS Sep 15, 2026
f4b3862
fix(content-drive): make the joined field actually join, with Tailwin…
KevinDavilaDotCMS Sep 15, 2026
97e2c7f
refactor(content-drive): rebuild the scope control on PrimeNG's own r…
KevinDavilaDotCMS Sep 15, 2026
9c9c47d
fix(content-drive): pin the trigger's width and match its border to t…
KevinDavilaDotCMS Sep 15, 2026
3fb5e6c
fix(content-drive): match the trigger's background and text colour too
KevinDavilaDotCMS Sep 15, 2026
1f995a5
fix(content-drive): replace the important-modifier with pt; freeze th…
KevinDavilaDotCMS Sep 15, 2026
b817805
test(content-drive): isolation coverage (T063) and Postman contract c…
KevinDavilaDotCMS Sep 15, 2026
606707d
spec(content-drive): amend FR-029 and FR-003 to match what shipped
KevinDavilaDotCMS Sep 15, 2026
4c3fe91
test(browser): update BrowserAPITest for FR-027's escaping fix (SC-00…
KevinDavilaDotCMS Sep 15, 2026
adbc41a
spec(content-drive): fix convergence findings — stale AC text, missin…
KevinDavilaDotCMS Sep 15, 2026
2d4f81d
Removed .sdkmanrc from core-web
KevinDavilaDotCMS Sep 15, 2026
5f2634a
fix(content-drive): move scope explanation to each panel option
KevinDavilaDotCMS Sep 15, 2026
e9be7b9
Merge branch 'main' into issue-37479-content-drive-search-scope-impl
KevinDavilaDotCMS Sep 15, 2026
6d87d90
refactor(content-drive): streamline search input styling and enhance …
KevinDavilaDotCMS Sep 15, 2026
7f39e67
fix(content-drive): address PR #37554 review feedback
KevinDavilaDotCMS Sep 16, 2026
07f0da9
perf(content-drive): drop title_dotraw from non-leading Title-scope w…
KevinDavilaDotCMS Sep 16, 2026
7497df2
refactor(content-drive): enhance search input layout and icon stacking
KevinDavilaDotCMS Sep 17, 2026
a477b2e
feat(content-drive): implement search scope functionality and enhance…
KevinDavilaDotCMS Sep 17, 2026
81eb1ca
Manually merge with main
KevinDavilaDotCMS Sep 17, 2026
0abd0d6
feat(content-drive): Title / All Fields search scope + literal-text s…
zJaaal Sep 17, 2026
8109b9e
fix(content-drive): update search input and store methods for null ha…
KevinDavilaDotCMS Sep 17, 2026
81a9c50
feat(content-drive): enhance expectNoSearchWhile method to support pa…
KevinDavilaDotCMS Sep 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions core-web/apps/dotcms-ui-e2e/src/pages/contentDrive.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ import { Portlet } from '@utils/portlets';
*/
const OUTCOME_TIMEOUT = 60000;

/**
* The part of the `/api/v1/drive/search` request body the scope tests assert on. Deliberately
* partial — the full form is the backend's business — so a new server-side field does not break
* these tests, while the two that matter (`text`, `searchScope`) are named.
*/
interface DriveSearchPayload {
filters: {
text?: string;
searchScope?: string;
};
}

export class ContentDrivePage {
readonly toolbar: Locator;
readonly treeSelector: Locator;
Expand Down Expand Up @@ -136,6 +148,89 @@ export class ContentDrivePage {
});
}

/**
* Runs `action` and returns the JSON request body of the drive search it triggers.
*
* The wait is armed BEFORE `action` runs — the only order that works — and the predicate also
* pins the method and status, so a failing search fails here at the capture rather than
* downstream as a missing row.
*
* `matches` guards against a race the portlet's own startup creates: it can still be settling
* its initial listing when this is called, and those searches carry their own (usually empty)
* text. Every attempt arms before anything pending can slip past, so `action`'s own request
* is never missed — a settling search only costs one round of the loop.
*/
async captureSearchPayload(
action: () => Promise<void>,
matches?: (payload: DriveSearchPayload) => boolean
): Promise<DriveSearchPayload> {
for (let attempt = 0; attempt < 6; attempt++) {
const search = this.page.waitForResponse(
(r) =>
r.url().includes('/api/v1/drive/search') &&
r.request().method() === 'POST' &&
r.status() === 200,
{ timeout: 15000 }
);

if (attempt === 0) {
await action();
}

const response = await search;
const payload = (await response.request().postDataJSON()) as DriveSearchPayload;

if (!matches || matches(payload)) {
return payload;
}
}

throw new Error('no drive search matching the expected payload was captured');
}

/**
* Runs `body` and fails if a drive search that matches `matches` was submitted while it did.
*
* The evidence a negative needs: "re-selecting the active scope must not re-search" (FR-006)
* cannot be asserted on copy or on rows that did not appear — only watching the wire can. The
* same pattern `expectNothingUploadedWhile` uses for uploads.
*
* The payloads are inspected rather than the URLs: without `matches` any drive search in the
* window is a violation, but with it a caller can name precisely what a violation would look
* like. That is what lets the scope test watch a term-carrying window and ignore a leftover
* startup search (an empty `text`) instead of draining it with extra round trips first — a
* re-search the click caused would carry the same term, so the predicate catches it all the
* same while the window stays open. The same evidence pattern, one request fewer to prove it.
*/
async expectNoSearchWhile(
body: () => Promise<void>,
matches?: (payload: DriveSearchPayload) => boolean
) {
const offenders: DriveSearchPayload[] = [];
const record = (request: Request) => {
if (request.url().includes('/api/v1/drive/search') && request.method() === 'POST') {
const payload = request.postDataJSON() as DriveSearchPayload;

if (!matches || matches(payload)) {
offenders.push(payload);
}
}
};

this.page.on('request', record);

try {
await body();
} finally {
this.page.off('request', record);
}

expect(
offenders,
'a drive search fired, so the scope re-selection was not ignored'
).toEqual([]);
}

/** Navigates into a folder by clicking its row in the tree. */
async openFolder(name: string) {
await this.treeNodeLabels.filter({ hasText: name }).first().click();
Expand Down
Loading
Loading