Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/clear-wasps-make.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@stackflow/plugin-history-sync": patch
---

fix(plugin-history-sync): respect initial route pattern
154 changes: 154 additions & 0 deletions extensions/plugin-history-sync/src/historySyncPlugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1463,4 +1463,158 @@ describe("historySyncPlugin", () => {
*/
expect(queryResponse.data.hello).toEqual("world");
});

test("historySyncPlugin - uses the initially entered route pattern among multiple patterns", async () => {
history = createMemoryHistory({
initialEntries: ["/foo"],
});

const coreStore = stackflow({
activityNames: ["Home"],
plugins: [
historySyncPlugin({
history,
routes: {
Home: ["/", "/foo", "/bar", "/baz"],
},
fallbackActivity: () => "Home",
}),
],
});

actions = makeActionsProxy({
actions: coreStore.actions,
});

expect(path(history.location)).toEqual("/foo");

await actions.push({
activityId: "a1",
activityName: "Home",
activityParams: {},
});

expect(path(history.location)).toEqual("/foo/");
});

test("historySyncPlugin - uses the corresponding pattern when entering with a different route pattern", async () => {
history = createMemoryHistory({
initialEntries: ["/bar"],
});

const coreStore = stackflow({
activityNames: ["Home", "Article"],
plugins: [
historySyncPlugin({
history,
routes: {
Home: ["/", "/foo", "/bar", "/baz"],
Article: "/articles/:articleId",
},
fallbackActivity: () => "Home",
}),
],
});

actions = makeActionsProxy({
actions: coreStore.actions,
});

expect(path(history.location)).toEqual("/bar");

await actions.push({
activityId: "a1",
activityName: "Article",
activityParams: {
articleId: "123",
},
});

expect(path(history.location)).toEqual("/articles/123/");

history.back();
await new Promise((resolve) => setTimeout(resolve, 100));

expect(path(history.location)).toEqual("/bar/");
});

test("historySyncPlugin - maintains the initially entered route pattern even during replace", async () => {
history = createMemoryHistory({
initialEntries: ["/baz"],
});

const coreStore = stackflow({
activityNames: ["Home", "Article"],
plugins: [
historySyncPlugin({
history,
routes: {
Home: ["/", "/foo", "/bar", "/baz"],
Article: "/articles/:articleId",
},
fallbackActivity: () => "Home",
}),
],
});

actions = makeActionsProxy({
actions: coreStore.actions,
});

expect(path(history.location)).toEqual("/baz");

await actions.push({
activityId: "a1",
activityName: "Article",
activityParams: {
articleId: "123",
},
});

expect(path(history.location)).toEqual("/articles/123/");

history.back();
await new Promise((resolve) => setTimeout(resolve, 100));

expect(path(history.location)).toEqual("/baz/");
});

test("historySyncPlugin - each activity maintains its own route pattern when entered with different patterns", async () => {
history = createMemoryHistory({
initialEntries: ["/foo"],
});

const coreStore = stackflow({
activityNames: ["Home", "SecondHome"],
plugins: [
historySyncPlugin({
history,
routes: {
Home: ["/", "/foo", "/bar"],
SecondHome: ["/second", "/second-foo", "/second-bar"],
},
fallbackActivity: () => "Home",
}),
],
});

actions = makeActionsProxy({
actions: coreStore.actions,
});

expect(path(history.location)).toEqual("/foo");

await actions.push({
activityId: "s1",
activityName: "SecondHome",
activityParams: {},
});

expect(path(history.location)).toEqual("/second/");

history.back();
await new Promise((resolve) => setTimeout(resolve, 100));

expect(path(history.location)).toEqual("/foo/");
});
});
63 changes: 43 additions & 20 deletions extensions/plugin-history-sync/src/historySyncPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,20 @@ export function historySyncPlugin<

const activityRoutes = sortActivityRoutes(normalizeActivityRouteMap(routes));

const findMatchingRoute = (activityName: string, path?: string) => {
if (path) {
// Try to find route that matches both activity name and path
const matchByPath = activityRoutes.find((route) => {
if (route.activityName !== activityName) return false;
const template = makeTemplate(route, options.urlPatternOptions);
return template.parse(path) !== null;
});
if (matchByPath) return matchByPath;
}
// Fallback to first route with matching activity name
return activityRoutes.find((r) => r.activityName === activityName)!;
};

return () => {
let pushFlag = 0;
let silentFlag = false;
Expand Down Expand Up @@ -322,7 +336,15 @@ export function historySyncPlugin<
).parse(currentPath) ??
urlSearchParamsToMap(pathToUrl(currentPath).searchParams),
activityContext: {
path: currentPath,
path: makeTemplate(
targetActivityRoute,
options.urlPatternOptions,
).fill(
makeTemplate(
targetActivityRoute,
options.urlPatternOptions,
).parse(currentPath) ?? {},
),
lazyActivityComponentRenderContext: {
shouldRenderImmediately: true,
},
Expand All @@ -342,9 +364,10 @@ export function historySyncPlugin<
const stack = getStack();
const rootActivity = stack.activities[0];

const match = activityRoutes.find(
(r) => r.activityName === rootActivity.name,
)!;
const activityPath = rootActivity.context && "path" in rootActivity.context
? (rootActivity.context.path as string)
: undefined;
const match = findMatchingRoute(rootActivity.name, activityPath);
const template = makeTemplate(match, options.urlPatternOptions);

const lastStep = last(rootActivity.steps);
Expand Down Expand Up @@ -505,10 +528,10 @@ export function historySyncPlugin<
return;
}

const match = activityRoutes.find(
(r) => r.activityName === activity.name,
)!;

const activityPath = activity.context && "path" in activity.context
? (activity.context.path as string)
: undefined;
const match = findMatchingRoute(activity.name, activityPath);
const template = makeTemplate(match, options.urlPatternOptions);

requestHistoryTick(() => {
Expand All @@ -529,10 +552,10 @@ export function historySyncPlugin<
return;
}

const match = activityRoutes.find(
(r) => r.activityName === activity.name,
)!;

const activityPath = activity.context && "path" in activity.context
? (activity.context.path as string)
: undefined;
const match = findMatchingRoute(activity.name, activityPath);
const template = makeTemplate(match, options.urlPatternOptions);

requestHistoryTick(() => {
Expand All @@ -553,10 +576,10 @@ export function historySyncPlugin<
return;
}

const match = activityRoutes.find(
(r) => r.activityName === activity.name,
)!;

const activityPath = activity.context && "path" in activity.context
? (activity.context.path as string)
: undefined;
const match = findMatchingRoute(activity.name, activityPath);
const template = makeTemplate(match, options.urlPatternOptions);

requestHistoryTick(() => {
Expand All @@ -576,10 +599,10 @@ export function historySyncPlugin<
return;
}

const match = activityRoutes.find(
(r) => r.activityName === activity.name,
)!;

const activityPath = activity.context && "path" in activity.context
? (activity.context.path as string)
: undefined;
const match = findMatchingRoute(activity.name, activityPath);
const template = makeTemplate(match, options.urlPatternOptions);

requestHistoryTick(() => {
Expand Down
Loading