feat: track selected market creators
This commit is contained in:
@@ -290,6 +290,140 @@ describe("market-content-entry", () => {
|
||||
expect((toolbar as HTMLElement | null)?.hidden).toBe(false);
|
||||
});
|
||||
|
||||
test("selection keeps a clicked creator checked after the table re-renders", async () => {
|
||||
document.body.innerHTML = buildMarketFixture();
|
||||
const mutationObserver = createMutationObserverFactory();
|
||||
|
||||
const { createMarketController } = await import("../src/content/market/index");
|
||||
const controller = trackController(createMarketController({
|
||||
document,
|
||||
loadAuthorMetrics: async () => ({
|
||||
success: false,
|
||||
reason: "request-failed"
|
||||
}),
|
||||
mutationObserverFactory: mutationObserver.factory,
|
||||
window
|
||||
}));
|
||||
|
||||
await controller.ready;
|
||||
clickSelectionCheckboxForAuthor("a");
|
||||
expect(readSelectionCheckboxForAuthor("a").checked).toBe(true);
|
||||
|
||||
const table = document.querySelector("[data-market-table]");
|
||||
if (!(table instanceof HTMLElement)) {
|
||||
throw new Error("Missing market table");
|
||||
}
|
||||
|
||||
table.outerHTML = buildMarketTableOnlyFixture();
|
||||
mutationObserver.trigger();
|
||||
await flushWithTimers();
|
||||
|
||||
expect(readSelectionCheckboxForAuthor("a").checked).toBe(true);
|
||||
});
|
||||
|
||||
test("selection survives a page change and re-render", async () => {
|
||||
const pages = [
|
||||
[
|
||||
{ authorId: "111", authorName: "达人 A", price21To60s: "¥11,000" },
|
||||
{ authorId: "222", authorName: "达人 B", price21To60s: "¥22,000" }
|
||||
],
|
||||
[
|
||||
{ authorId: "111", authorName: "达人 A", price21To60s: "¥11,000" },
|
||||
{ authorId: "333", authorName: "达人 C", price21To60s: "¥33,000" }
|
||||
]
|
||||
];
|
||||
document.body.innerHTML = buildRealMarketFixture(pages[0]);
|
||||
installAsyncPaginationHarness(pages);
|
||||
|
||||
const { createMarketController } = await import("../src/content/market/index");
|
||||
const controller = trackController(createMarketController({
|
||||
document,
|
||||
loadAuthorMetrics: async () => ({
|
||||
success: false,
|
||||
reason: "request-failed"
|
||||
}),
|
||||
window
|
||||
}));
|
||||
|
||||
await controller.ready;
|
||||
clickSelectionCheckboxForAuthor("111");
|
||||
click('[data-testid="next-page"]');
|
||||
await flushWithTimers();
|
||||
|
||||
expect(readSelectionCheckboxForAuthor("111").checked).toBe(true);
|
||||
expect(readSelectionCheckboxForAuthor("333").checked).toBe(false);
|
||||
});
|
||||
|
||||
test("selection header selects all visible creators on the current page", async () => {
|
||||
document.body.innerHTML = buildRealMarketFixture([
|
||||
{ authorId: "111", authorName: "达人 A", price21To60s: "¥11,000" },
|
||||
{ authorId: "222", authorName: "达人 B", price21To60s: "¥22,000" }
|
||||
]);
|
||||
|
||||
const { createMarketController } = await import("../src/content/market/index");
|
||||
const controller = trackController(createMarketController({
|
||||
document,
|
||||
loadAuthorMetrics: async () => ({
|
||||
success: false,
|
||||
reason: "request-failed"
|
||||
}),
|
||||
window
|
||||
}));
|
||||
|
||||
await controller.ready;
|
||||
clickHeaderSelectionCheckbox();
|
||||
|
||||
expect(readSelectionCheckboxForAuthor("111").checked).toBe(true);
|
||||
expect(readSelectionCheckboxForAuthor("222").checked).toBe(true);
|
||||
});
|
||||
|
||||
test("selection header clears all visible creators on the current page", async () => {
|
||||
document.body.innerHTML = buildRealMarketFixture([
|
||||
{ authorId: "111", authorName: "达人 A", price21To60s: "¥11,000" },
|
||||
{ authorId: "222", authorName: "达人 B", price21To60s: "¥22,000" }
|
||||
]);
|
||||
|
||||
const { createMarketController } = await import("../src/content/market/index");
|
||||
const controller = trackController(createMarketController({
|
||||
document,
|
||||
loadAuthorMetrics: async () => ({
|
||||
success: false,
|
||||
reason: "request-failed"
|
||||
}),
|
||||
window
|
||||
}));
|
||||
|
||||
await controller.ready;
|
||||
clickHeaderSelectionCheckbox();
|
||||
clickHeaderSelectionCheckbox();
|
||||
|
||||
expect(readSelectionCheckboxForAuthor("111").checked).toBe(false);
|
||||
expect(readSelectionCheckboxForAuthor("222").checked).toBe(false);
|
||||
});
|
||||
|
||||
test("selection header becomes indeterminate when only part of the current page is selected", async () => {
|
||||
document.body.innerHTML = buildRealMarketFixture([
|
||||
{ authorId: "111", authorName: "达人 A", price21To60s: "¥11,000" },
|
||||
{ authorId: "222", authorName: "达人 B", price21To60s: "¥22,000" }
|
||||
]);
|
||||
|
||||
const { createMarketController } = await import("../src/content/market/index");
|
||||
const controller = trackController(createMarketController({
|
||||
document,
|
||||
loadAuthorMetrics: async () => ({
|
||||
success: false,
|
||||
reason: "request-failed"
|
||||
}),
|
||||
window
|
||||
}));
|
||||
|
||||
await controller.ready;
|
||||
clickSelectionCheckboxForAuthor("111");
|
||||
|
||||
expect(readHeaderSelectionCheckbox().checked).toBe(false);
|
||||
expect(readHeaderSelectionCheckbox().indeterminate).toBe(true);
|
||||
});
|
||||
|
||||
test("hydrates current page rows on start", async () => {
|
||||
document.body.innerHTML = buildMarketFixture();
|
||||
|
||||
@@ -3138,6 +3272,50 @@ function click(selector: string) {
|
||||
element.click();
|
||||
}
|
||||
|
||||
function clickSelectionCheckboxForAuthor(authorId: string) {
|
||||
readSelectionCheckboxForAuthor(authorId).click();
|
||||
}
|
||||
|
||||
function clickHeaderSelectionCheckbox() {
|
||||
readHeaderSelectionCheckbox().click();
|
||||
}
|
||||
|
||||
function readSelectionCheckboxForAuthor(authorId: string) {
|
||||
const bySelectionAuthorId = document.querySelector(
|
||||
`[data-market-selection-author-id="${authorId}"]`
|
||||
) as HTMLInputElement | null;
|
||||
if (bySelectionAuthorId) {
|
||||
return bySelectionAuthorId;
|
||||
}
|
||||
|
||||
const bySyntheticRow = document.querySelector(
|
||||
`[data-market-row][data-author-id="${authorId}"] [data-market-selection-checkbox="row"]`
|
||||
) as HTMLInputElement | null;
|
||||
if (bySyntheticRow) {
|
||||
return bySyntheticRow;
|
||||
}
|
||||
|
||||
const byAuthorCell = document.querySelector(
|
||||
`[data-testid="author-cell-${authorId}"] [data-market-selection-checkbox="row"]`
|
||||
) as HTMLInputElement | null;
|
||||
if (byAuthorCell) {
|
||||
return byAuthorCell;
|
||||
}
|
||||
|
||||
throw new Error(`Missing selection checkbox for author: ${authorId}`);
|
||||
}
|
||||
|
||||
function readHeaderSelectionCheckbox() {
|
||||
const checkbox = document.querySelector(
|
||||
'[data-market-selection-checkbox="header"]'
|
||||
) as HTMLInputElement | null;
|
||||
if (!checkbox) {
|
||||
throw new Error("Missing header selection checkbox");
|
||||
}
|
||||
|
||||
return checkbox;
|
||||
}
|
||||
|
||||
function setInputValue(selector: string, value: string) {
|
||||
const element = document.querySelector(selector) as HTMLInputElement | null;
|
||||
if (!element) {
|
||||
|
||||
Reference in New Issue
Block a user