Skip to content

Commit

Permalink
Always replace in history on redirect in SPA navigation (#1631)
Browse files Browse the repository at this point in the history
* Always replace in history on redirect in SPA navigation

Otherwise, two pages are placed into the history during navigation,
making it impossible (or at least hard) to navigate back.

* Remove unnecesary SPA redirect JS test

All SPA redirects during navigation should use replace,
plain browser redirect also don't get placed into history

* Test for SPA redirect during navigation + back button
  • Loading branch information
exyi committed Apr 7, 2023
1 parent 1a76945 commit 5b892a9
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ export function performRedirect(url: string, replace: boolean, allowSpa: boolean
}

export async function handleRedirect(options: PostbackOptions, resultObject: any, response: Response, replace: boolean = false): Promise<DotvvmRedirectEventArgs> {
if (resultObject.replace != null) {
replace = resultObject.replace || replace;
}
replace = Boolean(resultObject.replace) || replace;
const url = resultObject.url;

// trigger redirect event
Expand Down
3 changes: 2 additions & 1 deletion src/Framework/Framework/Resources/Scripts/spa/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ export async function navigateCore(url: string, options: PostbackOptions, handle
updater.updateViewModelAndControls(response.result, replaceTypeInfo);
isSpaReady(true);
} else if (response.result.action === "redirect") {
await handleRedirect(options, response.result, response.response!);
// always replace current page in history on navigation redirect, otherwise back button doesn't work (only navigates back to redirect)
await handleRedirect(options, response.result, response.response!, /* replace */ true);
return { ...options, url };
}

Expand Down
37 changes: 0 additions & 37 deletions src/Framework/Framework/Resources/Scripts/tests/eventArgs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,14 +216,6 @@ const fetchDefinitions = {
allowSpa: true
} as any;
},
spaNavigateRedirectWithReplace: async <T>(url: string, init: RequestInit) => {
return {
action: "redirect",
url: "/newUrl",
allowSpa: true,
replace: true
} as any;
},
spaNavigateError: async <T>(url: string, init: RequestInit) => {
throw new DotvvmPostbackError({
type: "serverError",
Expand Down Expand Up @@ -484,35 +476,6 @@ test("spaNavigation + success", async () => {
test("spaNavigation + redirect", async () => {
fetchJson = fetchDefinitions.spaNavigateRedirect;

const cleanup = watchEvents(false);
try {

const link = document.createElement("a");
link.href = "/test";
await spa.handleSpaNavigation(link, (u: string) => {});

var history = getEventHistory();

let i = 2; // skip the "init" and "initCompleted" event
validateEvent(history[i++], "spaNavigating", "spaNavigation", validations.hasSender, validations.hasCancel, validations.hasUrl);
validateEvent(history[i++], "redirect", "spaNavigation", validations.hasSender, validations.hasResponse, validations.hasServerResponseObject, validations.hasUrl, validations.hasReplace);
validateEvent(history[i++], "spaNavigating", "spaNavigation", validations.hasCancel, validations.hasUrl);
validateEvent(history[i++], "newState", "postback");
validateEvent(history[i++], "spaNavigated", "spaNavigation", validations.hasResponse, validations.hasServerResponseObject, validations.hasUrl);

expect(history.length).toBe(i);
}
finally {
cleanup();
updateTypeInfo(typeMetadata)
replaceViewModel(originalViewModel.viewModel as RootViewModel);
}

});

test("spaNavigation + redirect with replace (new page is loaded without SPA)", async () => {
fetchJson = fetchDefinitions.spaNavigateRedirectWithReplace;

const cleanup = watchEvents(false);
try {

Expand Down
2 changes: 2 additions & 0 deletions src/Samples/Common/DotvvmStartup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ private static void AddRedirections(DotvvmConfiguration config)
newDict["Id"] = 1221;
return newDict;
});

config.RouteTable.AddRouteRedirection("ComplexSamples_SPA_redirect", "ComplexSamples/SPA/redirect", "ComplexSamples_SPA_test");
}

private static void AddRoutes(DotvvmConfiguration config)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<h2>Default</h2>

<dot:Button Click="{command: AddSampleText()}" Text="Validated command"/>
<dot:Button Click="{command: AddSampleText()}" id="validated-command" Text="Validated command"/>

<dot:Literal data-ui="sample-text" RenderSpanElement="true" Text="{value: SampleText}"/>
</dot:Content>
Expand Down
6 changes: 4 additions & 2 deletions src/Samples/Common/Views/ComplexSamples/SPA/site.dotmaster
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@

<h1>SPA Test</h1>

<dot:RouteLink RouteName="ComplexSamples_SPA_default" Text="Default Page" />
<dot:RouteLink RouteName="ComplexSamples_SPA_test" Text="Test Page" />
<dot:RouteLink RouteName="ComplexSamples_SPA_default" Text="Default Page" Id="link-default" />
<dot:RouteLink RouteName="ComplexSamples_SPA_test" Text="Test Page" Id="link-test" />
<dot:RouteLink RouteName="ComplexSamples_SPA_redirect" Text="Redirect to Test" Id="link-redirect" />
<dot:Button Text="Redirect to Test" Id="button-redirect" Click={command: DotvvmRequestContextExtensions.RedirectToRoute(Context, "ComplexSamples_SPA_test")} ButtonTagName=button Validation.Enabled=false />

<dot:SpaContentPlaceHolder ID="Container" />

Expand Down
2 changes: 1 addition & 1 deletion src/Samples/Common/Views/ComplexSamples/SPA/test.dothtml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<h2>Test</h2>

<dot:TextBox Text="{value: Name}" />
<dot:Button Click="{command: AddSampleText()}" Text="Validated command" />
<dot:Button Click="{command: AddSampleText()}" id="validated-command" Text="Validated command" />
<dot:Literal data-ui="sample-text" RenderSpanElement="true" Text="{value: SampleText}" />

</dot:Content>
64 changes: 62 additions & 2 deletions src/Samples/Tests/Tests/Complex/SPATests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public void Complex_SPA_ValidationAndNavigation()
browser.NavigateToUrl(SamplesRouteUrls.ComplexSamples_SPA_test);

// click to generate validation error
browser.Single("input[type=button]").Click();
browser.Single("#validated-command").Click();

// check if validation error is displayed
AssertUI.InnerTextEquals(browser.Single("span[data-ui='sample-text']"), string.Empty);
Expand All @@ -94,12 +94,72 @@ public void Complex_SPA_ValidationAndNavigation()
browser.ElementAt("a", 0).Click();

// click to check if validation error disapeared
browser.Single("input[type=button]").Click();
browser.Single("#validated-command").Click();
browser.WaitForPostback();
AssertUI.InnerTextEquals(browser.Single("span[data-ui='sample-text']"), "Sample Text");
});
}

[Fact]
[SampleReference(nameof(SamplesRouteUrls.ComplexSamples_SPA_default))]
[SampleReference(nameof(SamplesRouteUrls.ComplexSamples_SPA_test))]
public void Complex_SPA_RedirectingLink()
{
RunInAllBrowsers(browser => {
browser.NavigateToUrl("/");


browser.NavigateToUrl(SamplesRouteUrls.ComplexSamples_SPA_default);

// navigate to test
browser.Single("#link-redirect").Click();

AssertUI.UrlEquals(browser, browser.BaseUrl + SamplesRouteUrls.ComplexSamples_SPA_test);
AssertUI.InnerTextEquals(browser.Single("h2"), "Test");

// go to default page
browser.NavigateBack();

AssertUI.UrlEquals(browser, browser.BaseUrl + SamplesRouteUrls.ComplexSamples_SPA_default);
AssertUI.InnerTextEquals(browser.Single("h2"), "Default");

browser.NavigateForward();

AssertUI.UrlEquals(browser, browser.BaseUrl + SamplesRouteUrls.ComplexSamples_SPA_test);
AssertUI.InnerTextEquals(browser.Single("h2"), "Test");
});
}

[Fact]
[SampleReference(nameof(SamplesRouteUrls.ComplexSamples_SPA_default))]
[SampleReference(nameof(SamplesRouteUrls.ComplexSamples_SPA_test))]
public void Complex_SPA_RedirectingCommand()
{
RunInAllBrowsers(browser => {
browser.NavigateToUrl("/");


browser.NavigateToUrl(SamplesRouteUrls.ComplexSamples_SPA_default);

// navigate to test
browser.Single("#button-redirect").Click();

AssertUI.UrlEquals(browser, browser.BaseUrl + SamplesRouteUrls.ComplexSamples_SPA_test);
AssertUI.InnerTextEquals(browser.Single("h2"), "Test");

// go to default page
browser.NavigateBack();

AssertUI.UrlEquals(browser, browser.BaseUrl + SamplesRouteUrls.ComplexSamples_SPA_default);
AssertUI.InnerTextEquals(browser.Single("h2"), "Default");

browser.NavigateForward();

AssertUI.UrlEquals(browser, browser.BaseUrl + SamplesRouteUrls.ComplexSamples_SPA_test);
AssertUI.InnerTextEquals(browser.Single("h2"), "Test");
});
}

public SPATests(ITestOutputHelper output) : base(output)
{
}
Expand Down

0 comments on commit 5b892a9

Please sign in to comment.