Skip to content
Merged
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
37 changes: 19 additions & 18 deletions client/router.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,56 @@
import {updateParams} from './params';
import environment from './environment';
import extractLocation from '../shared/extractLocation';
import worker from './worker';
import page from './page';
import windowEvent from './windowEvent';
import client from './client';
import environment from './environment';
import page from './page';
import { updateParams } from './params';
import segments from './segments';
import windowEvent from './windowEvent';
import worker from './worker';

let redirectTimer = null;

class Router {

event = 'nullstack.router';

previous = null;
_changed = false;
_segments = segments;

constructor() {
const {hash, url} = extractLocation(window.location.pathname+window.location.search);
const { hash, url } = extractLocation(window.location.pathname + window.location.search);
this._url = url;
this._hash = hash;
}

async _popState() {
const {urlWithHash} = extractLocation(window.location.pathname+window.location.search);
const { urlWithHash } = extractLocation(window.location.pathname + window.location.search);
await this._update(urlWithHash, false);
}

async _update(target, push) {
const {url, path, hash, urlWithHash} = extractLocation(target);
this.previous = this.url;
const { url, path, hash, urlWithHash } = extractLocation(target);
clearTimeout(redirectTimer);
redirectTimer = setTimeout(async () => {
page.status = 200;
if(environment.mode === 'ssg') {
if (environment.mode === 'ssg') {
worker.fetching = true;
const api = '/index.json';
const endpoint = path === '/' ? api : path+api;
const endpoint = path === '/' ? api : path + api;
try {
const response = await fetch(endpoint);
const payload = await response.json(url);
client.memory = payload.instances;
for(const key in payload.page) {
for (const key in payload.page) {
page[key] = payload.page[key];
}
worker.responsive = true;
} catch(error) {
} catch (error) {
worker.responsive = false;
}
worker.fetching = false;
}
if(push) {
if (push) {
history.pushState({}, document.title, urlWithHash);
}
this._url = url;
Expand All @@ -65,11 +66,11 @@ class Router {
if (target.startsWith('http')) {
return (window.location.href = target);
}
const {url, hash, urlWithHash} = extractLocation(target);
if(url !== this._url || this._hash !== hash) {
const { url, hash, urlWithHash } = extractLocation(target);
if (url !== this._url || this._hash !== hash) {
await this._update(urlWithHash, true);
}
if(!hash) {
if (!hash) {
window.scroll(0, 0);
}
}
Expand All @@ -87,7 +88,7 @@ class Router {
}

set path(target) {
this._redirect(target+window.location.search);
this._redirect(target + window.location.search);
}

}
Expand Down
4 changes: 3 additions & 1 deletion server/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import extractLocation from '../shared/extractLocation';
import { generateBase } from './project';
export default class Router {

previous = null

constructor(request, response) {
this.request = request;
this.response = response;
Expand Down Expand Up @@ -38,5 +40,5 @@ export default class Router {
get base() {
return generateBase()
}

}
1 change: 1 addition & 0 deletions tests/src/RoutesAndParams.njs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class RoutesAndParams extends Nullstack {
<button data-params onclick={this.setParamsDate}> date </button>
<div data-date={params.date} />
<div data-hydrated-param={this.paramHydrated} />
<div data-previous={String(router.previous)} />
</div>
)
}
Expand Down
63 changes: 43 additions & 20 deletions tests/src/RoutesAndParams.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,24 @@ describe('RoutesAndParams /routes-and-params', () => {

test('entering a route stops other routes on same depth', async () => {
const element = await page.$('[data-other]');
expect(element).toBeFalsy();
expect(element).toBeFalsy();
});

test('params keys return empty keys by default', async () => {
const element = await page.$('[data-empty]');
expect(element).toBeTruthy();
expect(element).toBeTruthy();
});

test('a tags can generate the href from params', async () => {
const element = await page.$('[href="/routes-and-params?framework=nullstack"]');
expect(element).toBeTruthy();
expect(element).toBeTruthy();
});

test('assignments to params convert the value to JSON', async () => {
await page.click('[data-params]');
await page.waitForSelector('[data-date="1992-10-16T00:00:00.000Z"]');
const element = await page.$('[data-date="1992-10-16T00:00:00.000Z"]');
expect(element).toBeTruthy();
expect(element).toBeTruthy();
});

});
Expand All @@ -67,12 +67,12 @@ describe('RoutesAndParams /routes-and-params/a', () => {

test('a tags update the router url', async () => {
const element = await page.$('[data-a]');
expect(element).toBeTruthy();
expect(element).toBeTruthy();
});

test('a custom event is triggered when the url changes', async () => {
const element = await page.$('[data-event-triggered]');
expect(element).toBeTruthy();
expect(element).toBeTruthy();
});

test('params are available when coming from external', async () => {
Expand All @@ -97,22 +97,22 @@ describe('RoutesAndParams /routes-and-params/?boolean=true', () => {

test('params with value of true are converted to boolean', async () => {
const element = await page.$('[data-boolean]');
expect(element).toBeTruthy();
expect(element).toBeTruthy();
});

test('a tags can assign directly to path', async () => {
const element = await page.$('[href="/routes-and-params/d?boolean=true"]');
expect(element).toBeTruthy();
expect(element).toBeTruthy();
});

test('router url removes the leading slash', async () => {
const element = await page.$('[data-url="/routes-and-params?boolean=true"]');
expect(element).toBeTruthy();
expect(element).toBeTruthy();
});

test('router path removes the leading slash', async () => {
const element = await page.$('[data-path="/routes-and-params"]');
expect(element).toBeTruthy();
expect(element).toBeTruthy();
});

});
Expand All @@ -128,7 +128,7 @@ describe('RoutesAndParams /routes-and-params?boolean=false', () => {

test('params with value of false are converted to boolean', async () => {
const element = await page.$('[data-boolean]');
expect(element).toBeFalsy();
expect(element).toBeFalsy();
});

});
Expand All @@ -144,12 +144,12 @@ describe('RoutesAndParams /routes-and-params/c', () => {

test('wildcards are matched last', async () => {
const element = await page.$('[data-id="c"]');
expect(element).toBeTruthy();
expect(element).toBeTruthy();
});

test('dynamic segments are assigned to params', async () => {
const element = await page.$('[data-id="c"]');
expect(element).toBeTruthy();
expect(element).toBeTruthy();
});

});
Expand All @@ -165,7 +165,7 @@ describe('RoutesAndParams /routes-and-params/c', () => {

test('wildcards can be prefixed', async () => {
const element = await page.$('[data-wildcard]');
expect(element).toBeTruthy();
expect(element).toBeTruthy();
});

});
Expand All @@ -181,12 +181,12 @@ describe('RoutesAndParams /routes-and-params/d?boolean=true#hash ssr', () => {

test('hash is not part of the router url', async () => {
const element = await page.$('[data-url="/routes-and-params/d?boolean=true"]');
expect(element).toBeTruthy();
expect(element).toBeTruthy();
});

test('hash is not part of the router path', async () => {
const element = await page.$('[data-path="/routes-and-params/d"]');
expect(element).toBeTruthy();
expect(element).toBeTruthy();
});

});
Expand All @@ -204,12 +204,12 @@ describe('RoutesAndParams /routes-and-params/d?boolean=true#hash spa', () => {

test('hash is not part of the router url', async () => {
const element = await page.$('[data-url="/routes-and-params/no-hash"]');
expect(element).toBeTruthy();
expect(element).toBeTruthy();
});

test('hash is not part of the router path', async () => {
const element = await page.$('[data-path="/routes-and-params/no-hash"]');
expect(element).toBeTruthy();
expect(element).toBeTruthy();
});

});
Expand All @@ -227,7 +227,7 @@ describe('RoutesAndParams /routes-and-params', () => {
await page.click('[href="https://nullstack.app"]');
await page.waitForSelector('[href="/contributors"]');
const url = await page.url();
expect(url).toMatch('https://nullstack.app');
expect(url).toMatch('https://nullstack.app');
});

});
Expand All @@ -245,7 +245,30 @@ describe('RoutesAndParams /routes-and-params', () => {
await page.click('[data-absolute]');
await page.waitForSelector('[href="/contributors"]');
const url = await page.url();
expect(url).toMatch('https://nullstack.app');
expect(url).toMatch('https://nullstack.app');
});

});

describe('RoutesAndParams /routes-and-params?previous=true', () => {

let page;

beforeAll(async () => {
page = await browser.newPage();
await page.goto('http://localhost:6969/routes-and-params?previous=true');
});

test('router previous key starts null', async () => {
const element = await page.$('[data-previous="null"]');
expect(element).toBeTruthy();
});

test('router previous is assigned with the old router url when route updates', async () => {
await page.click('[href="/routes-and-params?framework=nullstack"]');
await page.waitForSelector('[data-previous="/routes-and-params?previous=true"]');
const element = await page.$('[data-previous="/routes-and-params?previous=true"]');
expect(element).toBeTruthy();
});

});
Expand Down