diff --git a/vuu-ui/cypress.config.ts b/vuu-ui/cypress.config.ts
index d0e74de288..47eb6a9863 100644
--- a/vuu-ui/cypress.config.ts
+++ b/vuu-ui/cypress.config.ts
@@ -33,6 +33,7 @@ export default defineConfig({
viewportHeight: 1024,
video: false,
component: {
+ scrollBehavior: false,
setupNodeEvents(on, config) {
// installCoverageTask(on, config);
//Setting up a log task to allow logging to the console during an axe test because console.log() does not work directly in a test
diff --git a/vuu-ui/packages/vuu-table/src/Row.tsx b/vuu-ui/packages/vuu-table/src/Row.tsx
index 5a2aa70056..e7ff5fd12a 100644
--- a/vuu-ui/packages/vuu-table/src/Row.tsx
+++ b/vuu-ui/packages/vuu-table/src/Row.tsx
@@ -14,23 +14,11 @@ import {
RowSelected,
} from "@finos/vuu-utils";
import cx from "clsx";
-import { CSSProperties, memo, MouseEvent, useCallback, useEffect } from "react";
+import { CSSProperties, memo, MouseEvent, useCallback } from "react";
import { TableCell, TableGroupCell } from "./table-cell";
import "./Row.css";
-// const cellStyle = { background: "green", width: 150 };
-// const MyCell = () => {
-// useEffect(() => {
-// console.log("MyCell mounted");
-// return () => {
-// console.log("MyCell unmounted");
-// };
-// }, []);
-
-// return
;
-// };
-
export interface RowProps {
className?: string;
columnMap: ColumnMap;
diff --git a/vuu-ui/packages/vuu-table/src/__tests__/__component__/Table.cy.tsx b/vuu-ui/packages/vuu-table/src/__tests__/__component__/Table.cy.tsx
index a733530f32..a3b9a018c5 100644
--- a/vuu-ui/packages/vuu-table/src/__tests__/__component__/Table.cy.tsx
+++ b/vuu-ui/packages/vuu-table/src/__tests__/__component__/Table.cy.tsx
@@ -1,18 +1,41 @@
import React from "react";
// TODO try and get TS path alias working to avoid relative paths like this
-import { Instruments } from "../../../../../showcase/src/examples/Table/SIMUL.examples";
+import { SimulTable } from "../../../../../showcase/src/examples/Table/SIMUL.examples";
+import { TestTable } from "../../../../../showcase/src/examples/Table/Table.examples";
+import { assertRenderedRows } from "./table-test-utils";
+
+const withAriaIndex = (index: number) => ({
+ name: (_: string, el: Element) => el.ariaRowIndex === `${index}`,
+});
describe("WHEN it initially renders", () => {
+ const RENDER_BUFFER = 5;
+ const ROW_COUNT = 1000;
+ const tableConfig = {
+ renderBufferSize: RENDER_BUFFER,
+ headerHeight: 25,
+ height: 625,
+ rowCount: ROW_COUNT,
+ rowHeight: 20,
+ width: 1000,
+ };
+
it("THEN expected classname is present", () => {
cy.mount(
-
);
const container = cy.findByTestId("table");
container.should("have.class", "vuuTable");
});
+
+ it("THEN expected number of rows are present, with buffered rows, all with correct aria index", () => {
+ cy.mount();
+ assertRenderedRows({ from: 0, to: 30 }, RENDER_BUFFER, ROW_COUNT);
+ });
});
diff --git a/vuu-ui/packages/vuu-table/src/__tests__/__component__/Table.scrolling.cy.tsx b/vuu-ui/packages/vuu-table/src/__tests__/__component__/Table.scrolling.cy.tsx
index a06e1ab775..c520a6b93b 100644
--- a/vuu-ui/packages/vuu-table/src/__tests__/__component__/Table.scrolling.cy.tsx
+++ b/vuu-ui/packages/vuu-table/src/__tests__/__component__/Table.scrolling.cy.tsx
@@ -1,31 +1,184 @@
-import React from "react";
// TODO try and get TS path alias working to avoid relative paths like this
import { TestTable } from "../../../../../showcase/src/examples/Table/Table.examples";
+import { assertRenderedRows, withAriaIndex } from "./table-test-utils";
-const withAriaIndex = (index: number) => ({
- name: (_: string, el: HTMLElement) => el.ariaRowIndex === `${index}`,
-});
+describe("Table scrolling and keyboard navigation", () => {
+ const RENDER_BUFFER = 5;
+ const ROW_COUNT = 1000;
+ const tableConfig = {
+ renderBufferSize: RENDER_BUFFER,
+ headerHeight: 25,
+ height: 625,
+ rowCount: ROW_COUNT,
+ rowHeight: 20,
+ width: 1000,
+ };
+ describe("Page Keys", () => {
+ describe("WHEN first cell is focussed and page down pressed", () => {
+ it("THEN table scrolls down and next page of rows are rendered, first cell of new page is focussed", () => {
+ cy.mount();
+
+ // interestingly, realClick doesn't work here
+ cy.findByRole("cell", { name: "row 1" }).click();
+ cy.findByRole("cell", { name: "row 1" }).should(
+ "have.attr",
+ "tabindex",
+ "0"
+ );
+ cy.findByRole("cell", { name: "row 1" }).should("be.focused");
+ cy.realPress("PageDown");
+
+ cy.findByRole("row", withAriaIndex(25)).should("not.exist");
+ cy.findByRole("row", withAriaIndex(26)).should("exist");
+
+ cy.get(".vuuTable-contentContainer")
+ .then((el) => el[0].scrollTop)
+ .should("equal", 600);
+
+ // row 31 should be top row in viewport
+ cy.findByRole("row", withAriaIndex(31)).should(
+ "have.css",
+ "transform",
+ "matrix(1, 0, 0, 1, 0, 600)"
+ );
+
+ cy.findByRole("cell", { name: "row 31" }).should(
+ "have.attr",
+ "tabindex",
+ "0"
+ );
+ cy.findByRole("cell", { name: "row 31" }).should("be.focused");
+ });
+
+ describe("AND WHEN page up is then pressed", () => {
+ it("THEN table is back to original state, and first cell is once again focussed", () => {
+ cy.mount();
+
+ // interestingly, realClick doesn't work here
+ cy.findByRole("cell", { name: "row 1" }).click();
+
+ cy.realPress("PageDown");
+ cy.wait(60);
+ cy.realPress("PageUp");
+
+ cy.findByRole("cell", { name: "row 1" }).should(
+ "have.attr",
+ "tabindex",
+ "0"
+ );
+ cy.findByRole("cell", { name: "row 1" }).should("be.focused");
+
+ assertRenderedRows({ from: 0, to: 30 }, RENDER_BUFFER, ROW_COUNT);
+ });
+ });
+ });
+ });
+
+ describe("Home / End Keys", () => {
+ describe("WHEN topmost rows are in viewport, first cell is focussed and Home key pressed ", () => {
+ it("THEN nothing changes", () => {
+ cy.mount();
+ // interestingly, realClick doesn't work here
+ cy.findByRole("cell", { name: "row 1" }).click();
+ cy.realPress("Home");
+ cy.findByRole("cell", { name: "row 1" }).should(
+ "have.attr",
+ "tabindex",
+ "0"
+ );
+ cy.findByRole("cell", { name: "row 1" }).should("be.focused");
+ assertRenderedRows({ from: 0, to: 30 }, RENDER_BUFFER, ROW_COUNT);
+ });
+ });
+ describe("WHEN topmost rows are in viewport, cell in middle of viewport is focussed and Home key pressed ", () => {
+ it("THEN no scrolling, but focus moves to first cell", () => {
+ cy.mount();
+ // interestingly, realClick doesn't work here
+ cy.findByRole("cell", { name: "row 5" }).click();
+ cy.realPress("Home");
+ cy.findByRole("cell", { name: "row 1" }).should(
+ "have.attr",
+ "tabindex",
+ "0"
+ );
+ cy.findByRole("cell", { name: "row 1" }).should("be.focused");
+ assertRenderedRows({ from: 0, to: 30 }, RENDER_BUFFER, ROW_COUNT);
+ });
+ });
+
+ describe("WHEN topmost rows are in viewport, first cell is focussed and End key pressed ", () => {
+ it("THEN scrolls to end of data, last cell is focussed (same column)", () => {
+ cy.mount();
+ // interestingly, realClick doesn't work here
+ cy.findByRole("cell", { name: "row 1" }).click();
+ cy.realPress("End");
+ cy.findByRole("cell", { name: "row 1,000" }).should(
+ "have.attr",
+ "tabindex",
+ "0"
+ );
+ cy.findByRole("cell", { name: "row 1,000" }).should("be.focused");
+ assertRenderedRows({ from: 970, to: 1000 }, RENDER_BUFFER, ROW_COUNT);
+ });
+ });
+
+ describe("WHEN topmost rows are in viewport, cell mid viewport focussed and End key pressed ", () => {
+ it("THEN scrolls to end of data, last cell is focussed (same column)", () => {
+ cy.mount();
+ // interestingly, realClick doesn't work here
+ cy.findByRole("cell", { name: "row 10" }).click();
+ cy.realPress("End");
+ cy.findByRole("cell", { name: "row 1,000" }).should(
+ "have.attr",
+ "tabindex",
+ "0"
+ );
+ cy.findByRole("cell", { name: "row 1,000" }).should("be.focused");
+ assertRenderedRows({ from: 970, to: 1000 }, RENDER_BUFFER, ROW_COUNT);
+ });
+ });
+ });
+
+ describe("Arrow Up / Down Keys", () => {
+ describe("WHEN topmost rows are in viewport, first cell is focussed and Down Arrow key pressed ", () => {
+ it("THEN no scrolling, focus moved down to next cell", () => {
+ cy.mount();
+ // interestingly, realClick doesn't work here
+ cy.findByRole("cell", { name: "row 1" }).click();
+ cy.realPress("ArrowDown");
+ cy.findByRole("cell", { name: "row 2" }).should(
+ "have.attr",
+ "tabindex",
+ "0"
+ );
+ cy.findByRole("cell", { name: "row 2" }).should("be.focused");
+ assertRenderedRows({ from: 0, to: 30 }, RENDER_BUFFER, ROW_COUNT);
+ });
+ });
+ describe("WHEN topmost rows are in viewport, first cell in last row is focussed and Down Arrow key pressed ", () => {
+ it("THEN scroll down by 1 row, cell in bottom row has focus", () => {
+ cy.mount();
+ // interestingly, realClick doesn't work here
+ cy.findByRole("cell", { name: "row 30" }).click();
+ cy.realPress("ArrowDown");
+ cy.findByRole("cell", { name: "row 31" }).should(
+ "have.attr",
+ "tabindex",
+ "0"
+ );
+ cy.findByRole("cell", { name: "row 31" }).should("be.focused");
+ assertRenderedRows({ from: 1, to: 31 }, RENDER_BUFFER, ROW_COUNT);
+ });
+ });
+ });
-describe("WHEN it initially renders", () => {
- it("THEN expected number of rows are present, with buffered rows, all with correct aria index", () => {
- cy.mount(
-
- );
-
- // Note the Table Headers row is included in count
- const container = cy.findAllByRole("row").should("have.length", 36);
- cy.findByRole("row", withAriaIndex(0)).should("not.exist");
- cy.findByRole("row", withAriaIndex(1)).should("be.visible");
- cy.findByRole("row", withAriaIndex(30)).should("be.visible");
- cy.findByRole("row", withAriaIndex(31)).should("not.be.visible");
- cy.findByRole("row", withAriaIndex(35)).should("not.be.visible");
- cy.findByRole("row", withAriaIndex(36)).should("not.exist");
+ describe("scrolling with Scrollbar", () => {
+ describe("WHEN scrolled down by a distance equating to 500 rows", () => {
+ it("THEN correct rows are within viewport", () => {
+ cy.mount();
+ cy.get(".vuuTable-scrollbarContainer").scrollTo(0, 10000);
+ assertRenderedRows({ from: 500, to: 530 }, RENDER_BUFFER, ROW_COUNT);
+ });
+ });
});
});
diff --git a/vuu-ui/packages/vuu-table/src/__tests__/__component__/table-test-utils.ts b/vuu-ui/packages/vuu-table/src/__tests__/__component__/table-test-utils.ts
new file mode 100644
index 0000000000..e566d3068b
--- /dev/null
+++ b/vuu-ui/packages/vuu-table/src/__tests__/__component__/table-test-utils.ts
@@ -0,0 +1,43 @@
+import { VuuRange } from "packages/vuu-protocol-types";
+
+export const withAriaIndex = (index: number) => ({
+ name: (_: string, el: Element) => el.ariaRowIndex === `${index}`,
+});
+
+export const assertRenderedRows = (
+ { from, to }: VuuRange,
+ renderBufferSize: number,
+ totalRowCount: number
+) => {
+ const leadingBufferedRows = from < renderBufferSize ? from : renderBufferSize;
+ const offsetFromEnd = totalRowCount - to;
+ const trailingBufferedRows =
+ offsetFromEnd < renderBufferSize
+ ? Math.min(0, offsetFromEnd)
+ : renderBufferSize;
+ const renderedRowCount =
+ to - from + leadingBufferedRows + trailingBufferedRows;
+
+ // Note the Table Headers row is included in count, hence the + 1
+ cy.findAllByRole("row").should("have.length", renderedRowCount + 1);
+
+ // we use the aria index for locators, which is 1 based
+ const firstRenderedRow = from - leadingBufferedRows + 1;
+ const firstVisibleRow = from + 1;
+ const lastVisibleRow = to;
+ const lastRenderedRow = to + trailingBufferedRows;
+
+ cy.findByRole("row", withAriaIndex(firstRenderedRow - 1)).should("not.exist");
+ cy.findByRole("row", withAriaIndex(firstVisibleRow)).should("be.visible");
+ cy.findByRole("row", withAriaIndex(lastVisibleRow)).should("be.visible");
+
+ if (trailingBufferedRows > 0) {
+ cy.findByRole("row", withAriaIndex(lastVisibleRow + 1)).should(
+ "not.be.visible"
+ );
+ cy.findByRole("row", withAriaIndex(lastRenderedRow)).should(
+ "not.be.visible"
+ );
+ }
+ cy.findByRole("row", withAriaIndex(lastRenderedRow + 1)).should("not.exist");
+};
diff --git a/vuu-ui/packages/vuu-table/src/useDataSource.ts b/vuu-ui/packages/vuu-table/src/useDataSource.ts
index 290199626f..90d6559e53 100644
--- a/vuu-ui/packages/vuu-table/src/useDataSource.ts
+++ b/vuu-ui/packages/vuu-table/src/useDataSource.ts
@@ -112,6 +112,7 @@ export const useDataSource = ({
const setRange = useCallback(
(range: VuuRange) => {
+ console.log(`set Range ${range.from} ${range.to}`);
if (!rangesAreSame(range, rangeRef.current)) {
const fullRange = getFullRange(range, renderBufferSize);
dataWindow.setRange(fullRange);
diff --git a/vuu-ui/packages/vuu-table/src/useTableScroll.ts b/vuu-ui/packages/vuu-table/src/useTableScroll.ts
index 4637ab16b2..455a7dee96 100644
--- a/vuu-ui/packages/vuu-table/src/useTableScroll.ts
+++ b/vuu-ui/packages/vuu-table/src/useTableScroll.ts
@@ -190,7 +190,7 @@ export const useTableScroll = ({
const firstRow = getRowAtPosition(scrollTop);
if (firstRow !== firstRowRef.current) {
firstRowRef.current = firstRow;
- setRange({ from: firstRow, to: firstRow + viewportRowCount + 1 });
+ setRange({ from: firstRow, to: firstRow + viewportRowCount });
}
},
[getRowAtPosition, onVerticalScroll, setRange, viewportRowCount]
@@ -368,7 +368,7 @@ export const useTableScroll = ({
onVerticalScrollInSitu?.(offset);
const firstRow = firstRowRef.current + offset;
firstRowRef.current = firstRow;
- setRange({ from: firstRow, to: firstRow + viewportRowCount + 1 });
+ setRange({ from: firstRow, to: firstRow + viewportRowCount });
} else {
const scrollBy =
direction === "down" ? appliedPageSize : -appliedPageSize;
diff --git a/vuu-ui/showcase/src/examples/Table/SIMUL.examples.tsx b/vuu-ui/showcase/src/examples/Table/SIMUL.examples.tsx
index f49d811732..7c83ef83f5 100644
--- a/vuu-ui/showcase/src/examples/Table/SIMUL.examples.tsx
+++ b/vuu-ui/showcase/src/examples/Table/SIMUL.examples.tsx
@@ -56,10 +56,11 @@ const getDefaultColumnConfig = (
}
};
-const SimulTable = ({
+export const SimulTable = ({
getDefaultColumnConfig,
+ height = 625,
renderBufferSize = 0,
- tableName,
+ tableName = "instruments",
...props
}: Partial & {
getDefaultColumnConfig?: DefaultColumnConfiguration;
@@ -96,6 +97,7 @@ const SimulTable = ({
);
};
-
-export const Instruments = () => ;
-Instruments.displaySequence = displaySequence++;
+SimulTable.displaySequence = displaySequence++;
export const InstrumentsExtended = () => (
);