Skip to content

Commit

Permalink
Merge pull request #23 from klown/feat/navigation-track-render-element
Browse files Browse the repository at this point in the history
feat: track the element to render the "go-back" palette into when navigating up a layer
  • Loading branch information
cindyli authored Oct 3, 2024
2 parents 24182ce + 0260994 commit 3558dd0
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 71 deletions.
6 changes: 4 additions & 2 deletions src/client/ActionBranchToPaletteCell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ const navigateToPalette = async (event: Event): Promise<void> => {
const button = event.currentTarget as HTMLElement;
speak(button.innerText);

const buttonsPaletteName = button.parentElement.getAttribute("data-palettename");
const branchToPaletteName = button.getAttribute("data-branchto");
const paletteDefinition = await paletteStore.getNamedPalette(branchToPaletteName, loadPaletteFromJsonFile);
if (paletteDefinition) {
const displayElement = button.parentElement.parentElement;
navigationStack.push(navigationStack.currentPalette);
const goBackPalette = await paletteStore.getNamedPalette(buttonsPaletteName);
navigationStack.push({ palette: goBackPalette, htmlElement: displayElement });
render (html`<${Palette} json=${paletteDefinition}/>`, displayElement);
navigationStack.currentPalette = paletteDefinition;
navigationStack.currentPalette = { palette: paletteDefinition, htmlElement: displayElement };
}
else {
console.error(`navigateToPalette(): Unable to locate the palette definition for ${branchToPaletteName}`);
Expand Down
10 changes: 5 additions & 5 deletions src/client/CommandGoBackCell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ const goBackToPalette = async (event: Event): Promise<void> => {
speak(button.innerText);

const paletteToGoBackTo = navigationStack.peek();
if (paletteToGoBackTo) {
const paletteDefinition = await paletteStore.getNamedPalette(paletteToGoBackTo.name, loadPaletteFromJsonFile);
if (paletteToGoBackTo && paletteToGoBackTo.palette) {
const paletteDefinition = await paletteStore.getNamedPalette(paletteToGoBackTo.palette.name, loadPaletteFromJsonFile);
if (paletteDefinition) {
const paletteContainer = document.getElementById(button.getAttribute("aria-controls")) || document.body;
navigationStack.popAndSetCurrent(paletteDefinition);
const paletteContainer = paletteToGoBackTo.htmlElement || document.getElementById(button.getAttribute("aria-controls")) || document.body;
navigationStack.popAndSetCurrent(paletteToGoBackTo);
render (html`<${Palette} json=${paletteDefinition}/>`, paletteContainer);
}
else {
console.error(`goBackToPalette(): Unable to locate the palette definition for ${paletteToGoBackTo}`);
console.error(`goBackToPalette(): Unable to locate the palette definition for ${paletteToGoBackTo.palette.name}`);
}
}
};
Expand Down
83 changes: 55 additions & 28 deletions src/client/NavigationStack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@
* https://github.com/inclusive-design/adaptive-palette/blob/main/LICENSE
*/

import { render, screen } from "@testing-library/preact";
import "@testing-library/jest-dom";
import { html } from "htm/preact";

import { NavigationStack } from "./NavigationStack";

const RENDERING_TEST_ID = "renderingDiv";

const testPalette1 = {
"name": "testPalette1",
"cells": {
Expand Down Expand Up @@ -56,86 +62,107 @@ const testPalette2 = {
}
};


describe("NavigationStack module - basics", (): void => {

const navigation = new NavigationStack();
let renderingElement;

beforeAll(async (): Promise<void> => {
render(html`<div data-testid="${RENDERING_TEST_ID}">Rendering div</div>`);
renderingElement = await screen.findByTestId(RENDERING_TEST_ID);
});

test("Empty NavigationStack", (): void => {
expect(navigation.isEmpty()).toBe(true);
expect(navigation.currentPalette).toBe(null);
});

test("Current palette accessors", (): void => {
navigation.currentPalette = testPalette1;
expect(navigation.currentPalette).toBe(testPalette1);
const testStackItem1 = {
palette: testPalette1,
htmlElement: renderingElement
};
navigation.currentPalette = testStackItem1;
expect(navigation.currentPalette).toBe(testStackItem1);
navigation.currentPalette = null;
expect(navigation.currentPalette).toBe(null);
});

test("Flush and reset the navigation stack", (): void => {
navigation.flushReset(testPalette2);
const testStackItem2 = {
palette: testPalette2,
htmlElement: renderingElement
};
navigation.flushReset(testStackItem2);
expect(navigation.isEmpty()).toBe(true);
expect(navigation.currentPalette).toBe(testPalette2);
expect(navigation.currentPalette).toBe(testStackItem2);
});
});

describe("NavigationStack module - pushing and popping", (): void => {

const navigation = new NavigationStack();
let renderingElement, testStackItem1, testStackItem2;

beforeEach ((): void => {
navigation.flushReset(null);
beforeAll(async (): Promise<void> => {
render(html`<div data-testid="${RENDERING_TEST_ID}">Rendering div</div>`);
renderingElement = await screen.findByTestId(RENDERING_TEST_ID);
testStackItem1 = { palette: testPalette1, htmlElement: renderingElement };
testStackItem2 = { palette: testPalette2, htmlElement: renderingElement };
});

test("Non-empty NavigationStack", (): void => {
navigation.push(testPalette1);
navigation.push(testStackItem1);
expect(navigation.isEmpty()).toBe(false);
expect(navigation.peek()).toBe(testPalette1);
expect(navigation.peek()).toBe(testStackItem1);
expect(navigation.currentPalette).toBe(null);
});

test("Pop the top of the stack", (): void => {
navigation.push(testPalette1);
navigation.flushReset(null);
navigation.push(testStackItem1);
const topPalette = navigation.pop();
expect(topPalette).toBe(testPalette1);
expect(topPalette).toBe(testStackItem1);
expect(navigation.isEmpty()).toBe(true);
// The current palette should be unaffected by a pop operation.
expect(navigation.currentPalette).toBe(null);
});

test("Multiple layers and a current palette", (): void => {
navigation.push(testPalette1);
navigation.push(testPalette2);
navigation.currentPalette = testPalette1;
navigation.push(testStackItem1);
navigation.push(testStackItem2);
navigation.currentPalette = testStackItem1;
expect(navigation.isEmpty()).toBe(false);
expect(navigation.peek()).toBe(testPalette2);
expect(navigation.peek(1)).toBe(testPalette1);
expect(navigation.currentPalette).toBe(testPalette1);
expect(navigation.peek()).toBe(testStackItem2);
expect(navigation.peek(1)).toBe(testStackItem1);
expect(navigation.currentPalette).toBe(testStackItem1);
});

test("Check invalid peek()", (): void => {
navigation.push(testPalette1);
navigation.push(testPalette2);
navigation.push(testStackItem1);
navigation.push(testStackItem2);
expect(navigation.isEmpty()).toBe(false);
expect(navigation.peek(-1)).toBe(undefined);
expect(navigation.peek(1024)).toBe(undefined);
});

test("Check pop and set current utility function", (): void => {
navigation.currentPalette = testPalette1;
navigation.push(testPalette1);
navigation.push(testPalette2);
const poppedPalette = navigation.popAndSetCurrent(testPalette2);
expect(poppedPalette).toBe(testPalette2);
expect(navigation.peek()).toBe(testPalette1);
expect(navigation.currentPalette).toBe(testPalette2);
navigation.currentPalette = testStackItem1;
navigation.push(testStackItem1);
navigation.push(testStackItem2);
const poppedPalette = navigation.popAndSetCurrent(testStackItem2);
expect(poppedPalette).toBe(testStackItem2);
expect(navigation.peek()).toBe(testStackItem1);
expect(navigation.currentPalette).toBe(testStackItem2);
});

test("Check peeking at the bottom of the stack", (): void => {
navigation.flushReset(null);
expect(navigation.isEmpty()).toBe(true);
expect(navigation.peekLast()).toBe(undefined);
navigation.push(testPalette1);
navigation.push(testPalette2);
expect(navigation.peekLast()).toBe(testPalette1);
navigation.push(testStackItem1);
navigation.push(testStackItem2);
expect(navigation.peekLast()).toBe(testStackItem1);
});
});
59 changes: 30 additions & 29 deletions src/client/NavigationStack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@
* https://github.com/inclusive-design/adaptive-palette/blob/main/LICENSE
*/

import { JsonPaletteType } from "./index.d";
import { NavStackItemType } from "./index.d";

export class NavigationStack {

// The actual stack keeping track of where the user was. The top-most palette
// is the most recent one the user was at before navigating to a new
// layer/palette.
navigateBackStack: Array<JsonPaletteType>;
navigateBackStack: Array<NavStackItemType>;

// The current palette in the palette display area
currPalette: JsonPaletteType;
currPalette: NavStackItemType;

/**
* Initialize the navigation stack to have zero entries.
Expand All @@ -38,12 +38,13 @@ export class NavigationStack {
}

/**
* Puah a palette onto the top of the navigation stack.
* @param: {JsonPaletteType} palette - The palette to push. If `null` or
* Puah a palette onto the top of the navigation stack and also remember where
* it was rendered.
* @param: {NavStackItemType} palette - The palette to push. If `null` or
* `undefined`, the navigation stack is
* left untouched.
*/
push (palette: JsonPaletteType): void {
push (palette: NavStackItemType): void {
if (!palette) {
return;
}
Expand All @@ -53,10 +54,10 @@ export class NavigationStack {
/**
* Pop and return the most recently pushed palette from the top of the
* navigation stack.
* @return {JsonPaletteType} - reference to the popped palette; null if the
* stack is empty.
* @return {NavStackItemType} - reference to the popped palette; null if the
* stack is empty.
*/
pop (): JsonPaletteType | null {
pop (): NavStackItemType | null {
if (this.isEmpty()) {
return null;
} else {
Expand All @@ -65,18 +66,18 @@ export class NavigationStack {
}

/**
* Return the palette at the top of the stack without changing the stack
* Return the stack item at the top of the stack without changing the stack
* itself. If an index is given, the palette at that index is returned. Note
* that an index of zero denotes the top of the stack.
* @param {integer} stackIndex - Optional: How far down the stack to peek,
* where zero is the top of the stack (default).
* If out of range, `undefined` is returned.
* @return {JsonPaletteType} - Reference to the palette at the top of the
* stack or at the given index; `undefined` if
* the given stack index is invalid -- negative or
* greater than the size of the stack.
* @return {NavStackItemType} - Reference to the palette at the top of the
* stack or at the given index; `undefined` if
* the given stack index is invalid -- negative
* or greater than the size of the stack.
*/
peek (stackIndex:number = 0): JsonPaletteType | undefined {
peek (stackIndex:number = 0): NavStackItemType | undefined {
// Flip the index value since Array.push() puts the item at the end
// of the array.
let palette = undefined;
Expand All @@ -88,11 +89,11 @@ export class NavigationStack {
}

/**
* Return the palette at the bottom of the stack without changing the stack.
* @return {JsonPaletteType} - Reference to the palette at the bottom of the
* stack, or `undefined` if the stack is empty.
* Return the stack item at the bottom of the stack without changing the stack.
* @return {NavStackItemType} - Reference to the palette at the bottom of the
* stack, or `undefined` if the stack is empty.
*/
peekLast (): JsonPaletteType | undefined {
peekLast (): NavStackItemType | undefined {
if (this.isEmpty()) {
return undefined;
} else {
Expand All @@ -103,37 +104,37 @@ export class NavigationStack {
/**
* Pop/return the most recently pushed palette and set the currently displayed
* palette to the given one.
* @param {JsonPaletteType} - The palette that is currently displayed, or
* is about to be displayed.
* @return {JsonPaletteType} - The most recently visited palette.
* @param {NavStackItemType} - The palette that is currently displayed, or
* is about to be displayed.
* @return {NavStackItemType} - The most recently visited palette.
*/
popAndSetCurrent (currentPalette: JsonPaletteType): JsonPaletteType | null {
popAndSetCurrent (currentPalette: NavStackItemType): NavStackItemType | null {
this.currentPalette = currentPalette;
return this.pop();
}

/**
* Empty the navigation stack and reset the current palette displayed.
* @param {JsonPaletteType} - The palette that is currently displayed.
* @param {NavStackItemType} - The palette that is currently displayed.
*/
flushReset (currentPalette: JsonPaletteType): void {
flushReset (currentPalette: NavStackItemType): void {
this.currentPalette = currentPalette;
this.navigateBackStack.length = 0;
}

/**
* Accessor for setting the currently displayed palette.
* @param: {JsonPaletteType} - the intended current palette.
* @param: {NavStackItemType} - the intended current palette.
*/
set currentPalette (palette: JsonPaletteType) {
set currentPalette (palette: NavStackItemType) {
this.currPalette = palette;
}

/**
* Accessor for getting the currently displayed palette.
* @return: {JsonPaletteType} - The current palette.
* @return: {NavStackItemType} - The current palette.
*/
get currentPalette(): JsonPaletteType {
get currentPalette(): NavStackItemType {
return this.currPalette;
}
}
18 changes: 11 additions & 7 deletions src/client/Palette.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ describe("Palette integration test", () => {
// insure that the entire palette is in the DOM.
render(html`<${Palette} json=${testPalette}/>`);
const navStack = adaptivePaletteGlobals.navigationStack;
navStack.currentPalette = testPalette;
navStack.currentPalette = { palette: testPalette, htmlElement: document.body };

const firstCell = await screen.findByText("First Cell");
expect(firstCell).toBeInTheDocument();
Expand Down Expand Up @@ -198,18 +198,22 @@ describe("Palette integration test", () => {
fireEvent.click(clearButton);
expect(contentArea.childNodes.length).toBe(0);

// Trigger forward navigation
const goForwardButton = await screen.findByText("Go To");
// Trigger forward navigation.
// Note: the element whose text is "Go To" is actually a <div> within the
// <button> of interest. The button is that <div>'s parent. Similarly
// for the "Back Up" button.
const goForwardButton = (await screen.findByText("Go To")).parentElement;

fireEvent.click(goForwardButton);
const goBackButton = await waitFor(() => screen.findByText("Back Up"));
const goBackButton = (await waitFor(() => screen.findByText("Back Up"))).parentElement;
expect(goBackButton).toBeInTheDocument();
expect(navStack.currentPalette).toBe(testLayerOnePalette);
expect(navStack.peek()).toBe(testPalette);
expect(navStack.currentPalette.palette).toBe(testLayerOnePalette);
expect(navStack.peek().palette).toBe(testPalette);

// Trigger go-back navigation
fireEvent.click(goBackButton);
await waitFor(() => expect(firstCell).toBeInTheDocument());
expect(navStack.currentPalette).toBe(testPalette);
expect(navStack.currentPalette.palette).toBe(testPalette);
expect(navStack.isEmpty()).toBe(true);
});
});
7 changes: 7 additions & 0 deletions src/client/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,10 @@ export type EncodingType = BlissSymbolInfoType & {
export type PaletteFileMapType = {
[paletteName: string]: string
}

// Items pushed to the navigation stack. The first field is the palette to go
// back to, the second field is where to render it in the document.
export type NavStackItemType = {
palette: JsonPaletteType,
htmlElement: HTMLElement
}

0 comments on commit 3558dd0

Please sign in to comment.