fix: recover favorites controller refresh

This commit is contained in:
wxs
2026-07-17 15:32:51 +08:00
parent 8ebb50524c
commit 01efa9f847
2 changed files with 69 additions and 5 deletions
+9 -2
View File
@@ -236,6 +236,7 @@ export function createMarketController(options: CreateMarketControllerOptions) {
let favoritesRefreshVersion = 0; let favoritesRefreshVersion = 0;
let favoriteActionButtons: HTMLButtonElement[] = []; let favoriteActionButtons: HTMLButtonElement[] = [];
let needsFavoritesRefresh = false; let needsFavoritesRefresh = false;
let favoritesUnavailable = false;
const favoritesDrawer: FavoritesDrawerController = createFavoritesDrawer( const favoritesDrawer: FavoritesDrawerController = createFavoritesDrawer(
options.document, options.document,
{ {
@@ -272,16 +273,19 @@ export function createMarketController(options: CreateMarketControllerOptions) {
const favoriteControlsMissing = const favoriteControlsMissing =
cachedFavoritesState !== undefined && cachedFavoritesState !== undefined &&
favoriteActionButtons.some((button) => !button.isConnected); favoriteActionButtons.some((button) => !button.isConnected);
const needsUnavailableFavoritesRecovery =
favoritesUnavailable && nextPageSignature !== lastKnownPageSignature;
if ( if (
nextPageSignature === lastKnownPageSignature && nextPageSignature === lastKnownPageSignature &&
!toolbarNeedsRemount && !toolbarNeedsRemount &&
!selectionControlsMissing && !selectionControlsMissing &&
!favoriteControlsMissing !favoriteControlsMissing &&
!needsUnavailableFavoritesRecovery
) { ) {
return; return;
} }
if (favoriteControlsMissing) { if (favoriteControlsMissing || needsUnavailableFavoritesRecovery) {
needsFavoritesRefresh = true; needsFavoritesRefresh = true;
} }
scheduleSync(); scheduleSync();
@@ -529,6 +533,7 @@ export function createMarketController(options: CreateMarketControllerOptions) {
const ready = (async () => { const ready = (async () => {
await runSyncCycle(); await runSyncCycle();
await refreshFavorites(); await refreshFavorites();
await waitForDomSettled();
})(); })();
return { return {
@@ -722,6 +727,7 @@ export function createMarketController(options: CreateMarketControllerOptions) {
} }
cachedFavoritesState = state; cachedFavoritesState = state;
favoritesUnavailable = false;
favoritesDrawer.setError(""); favoritesDrawer.setError("");
favoritesDrawer.render(state); favoritesDrawer.render(state);
runWithoutMutationSync(() => { runWithoutMutationSync(() => {
@@ -734,6 +740,7 @@ export function createMarketController(options: CreateMarketControllerOptions) {
cachedFavoritesState = undefined; cachedFavoritesState = undefined;
favoriteActionButtons = []; favoriteActionButtons = [];
favoritesUnavailable = true;
disposeFavoriteRowPickers(options.document); disposeFavoriteRowPickers(options.document);
favoritesDrawer.setError("收藏夹暂不可用"); favoritesDrawer.setError("收藏夹暂不可用");
} }
+60 -3
View File
@@ -4872,7 +4872,7 @@ describe("market-content-entry", () => {
expect(document.querySelector('[data-plugin-batch-submit="button"]')).not.toBeNull(); expect(document.querySelector('[data-plugin-batch-submit="button"]')).not.toBeNull();
}); });
test("clears stale favorite pickers when a later repository refresh fails", async () => { test("recovers favorite pickers after a later refresh failure and page replacement", async () => {
document.body.innerHTML = buildRealMarketFixture([ document.body.innerHTML = buildRealMarketFixture([
{ authorId: "111", authorName: "达人 A", price21To60s: "¥11,000" } { authorId: "111", authorName: "达人 A", price21To60s: "¥11,000" }
]); ]);
@@ -4881,12 +4881,13 @@ describe("market-content-entry", () => {
await repository.setCreatorFolderIds({ authorId: "111", authorName: "达人 A" }, [folder.id]); await repository.setCreatorFolderIds({ authorId: "111", authorName: "达人 A" }, [folder.id]);
const originalRead = repository.read.bind(repository); const originalRead = repository.read.bind(repository);
let shouldRejectRead = false; let shouldRejectRead = false;
repository.read = async () => { const read = vi.fn(async () => {
if (shouldRejectRead) { if (shouldRejectRead) {
throw new Error("storage unavailable"); throw new Error("storage unavailable");
} }
return originalRead(); return originalRead();
}; });
repository.read = read;
const observer = createMutationObserverFactory(); const observer = createMutationObserverFactory();
const { createMarketController } = await import("../src/content/market/index"); const { createMarketController } = await import("../src/content/market/index");
@@ -4916,6 +4917,62 @@ describe("market-content-entry", () => {
expect(document.querySelector('[data-sces-favorite-row-action="button"]')).toBeNull(); expect(document.querySelector('[data-sces-favorite-row-action="button"]')).toBeNull();
expect(document.querySelector('[data-plugin-export="button"]')).not.toBeNull(); expect(document.querySelector('[data-plugin-export="button"]')).not.toBeNull();
shouldRejectRead = false;
document.documentElement.setAttribute("data-test-page-index", "2");
document.querySelector('[data-testid="action-cell-111"]')!.replaceChildren(
document.createTextNode("下单")
);
observer.trigger();
await waitForCondition(
() =>
document.querySelector('[data-sces-favorite-row-action="button"]')?.dataset
.scesFavoriteState === "saved"
);
expect(read).toHaveBeenCalledTimes(3);
expect(document.querySelector('[data-sces-favorites-drawer="error"]')).toBeNull();
});
test("settles the initial observer resync before ready enables an immediate export", async () => {
document.body.innerHTML = buildRealMarketFixture([
{ authorId: "111", authorName: "达人 A", price21To60s: "¥11,000" }
]);
const repository = createTestFavoritesRepository();
const originalRead = repository.read.bind(repository);
const observer = createMutationObserverFactory();
let shouldScheduleInitialResync = true;
repository.read = async () => {
if (shouldScheduleInitialResync) {
shouldScheduleInitialResync = false;
document.querySelector('[data-plugin-toolbar="root"]')?.remove();
observer.trigger();
}
return originalRead();
};
const buildCsv = vi.fn(() => "csv-output");
const { createMarketController } = await import("../src/content/market/index");
const controller = trackController(createMarketController({
buildCsv,
document,
favoritesRepository: repository,
loadAuthorMetrics: async () => ({ success: false, reason: "request-failed" }),
mutationObserverFactory: observer.factory,
onCsvReady: vi.fn(),
window
}));
await controller.ready;
expect(document.querySelector('[data-plugin-toolbar="root"]')).not.toBeNull();
setSelectValue('[data-plugin-export-range="select"]', "current");
dispatchChange('[data-plugin-export-range="select"]');
removeDefaultSpreadMetricFilter();
click('[data-plugin-export="button"]');
await waitForMockCall(buildCsv, 40, 50);
expect(buildCsv).toHaveBeenCalledTimes(1);
}); });
test("keeps the friendly drawer error when a direct folder mutation has no message", async () => { test("keeps the friendly drawer error when a direct folder mutation has no message", async () => {