From 3cd1463d71946305746696b4b024ee390e2d1d6a Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Fri, 14 Jun 2024 14:52:25 -0700 Subject: [PATCH 1/3] Table coverage --- .../frontend/core/components/BasicTable.js | 7 +- .../core/components/JSONSchemaForm.js | 8 ++ .../frontend/core/components/SimpleTable.js | 7 +- .../frontend/core/components/Table.js | 34 +++---- tests/components/forms.test.ts | 5 +- tests/components/table.test.ts | 92 +++++++++++++++++++ 6 files changed, 127 insertions(+), 26 deletions(-) create mode 100644 tests/components/table.test.ts diff --git a/src/electron/frontend/core/components/BasicTable.js b/src/electron/frontend/core/components/BasicTable.js index ad047d6d2..3ac933bc9 100644 --- a/src/electron/frontend/core/components/BasicTable.js +++ b/src/electron/frontend/core/components/BasicTable.js @@ -450,6 +450,10 @@ export class BasicTable extends LitElement { for (let key in this.ignore) delete entries[key]; for (let key in this.ignore["*"] ?? {}) delete entries[key]; + const schemaOrder = this.#itemSchema.order ?? [] + const order = this.keyColumn ? [this.keyColumn, ...schemaOrder] : schemaOrder; + + // Sort Columns by Key Column and Requirement const keys = (this.#keys = @@ -459,8 +463,7 @@ export class BasicTable extends LitElement { ...this.#itemSchema, properties: entries, }, - this.keyColumn, - this.#itemSchema.order + order )); // Try to guess the key column if unspecified diff --git a/src/electron/frontend/core/components/JSONSchemaForm.js b/src/electron/frontend/core/components/JSONSchemaForm.js index 57c5b0f5e..581b4f681 100644 --- a/src/electron/frontend/core/components/JSONSchemaForm.js +++ b/src/electron/frontend/core/components/JSONSchemaForm.js @@ -1459,6 +1459,14 @@ export class JSONSchemaForm extends LitElement { this.#resetLoadState(); + // this.updateComplete.then(() => { + // this.#toggleRendered(); // Toggle internal render state + + // // Promise.all([...Object.values(this.forms), ...Object.values(this.tables)]).map(o => o.updateComplete).then(() => { + // // this.#toggleRendered(); // Toggle internal render state + // // }) + // }) + const schema = this.schema ?? {}; this.resolved = structuredClone(this.results); // Track resolved values as a copy of the user-specified results diff --git a/src/electron/frontend/core/components/SimpleTable.js b/src/electron/frontend/core/components/SimpleTable.js index 1db95ee1a..2ad81d520 100644 --- a/src/electron/frontend/core/components/SimpleTable.js +++ b/src/electron/frontend/core/components/SimpleTable.js @@ -968,14 +968,17 @@ export class SimpleTable extends LitElement { for (let key in this.ignore) delete entries[key]; for (let key in this.ignore["*"] ?? {}) delete entries[key]; + const schemaOrder = this.#itemSchema.order ?? [] + const order = this.keyColumn ? [this.keyColumn, ...schemaOrder] : schemaOrder; + if (!this.keyColumn && !order.includes('name')) order.unshift('name'); + // Sort Columns by Key Column and Requirement this.colHeaders = sortTable( { ...this.#itemSchema, properties: entries, }, - this.keyColumn, - this.#itemSchema.order ?? ["name"] // Specify the order of the columns + order // Specify the order of the columns ); // Try to guess the key column if unspecified diff --git a/src/electron/frontend/core/components/Table.js b/src/electron/frontend/core/components/Table.js index 6c7a66983..b1ebd5bcd 100644 --- a/src/electron/frontend/core/components/Table.js +++ b/src/electron/frontend/core/components/Table.js @@ -12,9 +12,7 @@ const rowSymbol = Symbol("row"); const maxRows = 20; -const isRequired = (col, schema) => { - return schema.required?.includes(col); -}; +const isRequired = (col, schema) => schema.required?.includes(col) export const getEditable = (value, rowData = {}, config, colName) => { if (typeof config === "boolean") return config; @@ -22,7 +20,8 @@ export const getEditable = (value, rowData = {}, config, colName) => { return getEditable(value, rowData, config?.[colName] ?? true); }; -export function sortTable(schema, keyColumn, order) { +export function sortTable(schema, order = []) { + const cols = Object.keys(schema.properties) //Sort alphabetically @@ -39,21 +38,16 @@ export function sortTable(schema, keyColumn, order) { if (bRequired) return 1; return 0; }) - .sort((a, b) => { - if (a === keyColumn) return -1; - if (b === keyColumn) return 1; - return 0; - }); - return order - ? cols.sort((a, b) => { - const idxA = order.indexOf(a); - const idxB = order.indexOf(b); - if (idxA === -1) return 1; - if (idxB === -1) return -1; - return idxA - idxB; - }) - : cols; + + + return cols.sort((a, b) => { + const idxA = order.indexOf(a); + const idxB = order.indexOf(b); + if (idxA === -1) return 1; + if (idxB === -1) return -1; + return idxA - idxB; + }) } // Inject scoped stylesheet @@ -263,14 +257,14 @@ export class Table extends LitElement { } // Sort Columns by Key Column and Requirement + const order = this.keyColumn ? [this.keyColumn, ...this.#itemSchema.order ?? []] : this.#itemSchema.order; const colHeaders = (this.colHeaders = sortTable( { ...this.#itemSchema, properties: entries, }, - this.keyColumn, - this.#itemSchema.order + order )); // Try to guess the key column if unspecified diff --git a/tests/components/forms.test.ts b/tests/components/forms.test.ts index a609f1698..f4d32ea28 100644 --- a/tests/components/forms.test.ts +++ b/tests/components/forms.test.ts @@ -19,6 +19,7 @@ async function mountComponent(props) { document.body.append(form) await form.rendered + // await form.updateComplete; return form; } @@ -163,10 +164,10 @@ describe('JSONSchemaForm', () => { const row = users.getRow(0) const newData = { name: 'John Doe', age: 30 } - await Promise.all(Object.entries(newData).map(([key, value]) => { + Object.entries(newData).map(([key, value]) => { const cell = row.find(cell => cell.simpleTableInfo.col === key) return cell.setInput(value) - })) + }) await sleep(100) // Wait for updates to register on the table diff --git a/tests/components/table.test.ts b/tests/components/table.test.ts new file mode 100644 index 000000000..5f77b4b7e --- /dev/null +++ b/tests/components/table.test.ts @@ -0,0 +1,92 @@ +// tests/table.test.js +import { expect, test, describe, vi, beforeEach, afterEach } from 'vitest'; +import { Table, sortTable } from '../../src/electron/frontend/core/components/Table.js'; +import { sleep } from '../puppeteer.js'; + +global.ResizeObserver = global.IntersectionObserver = vi.fn().mockImplementation(() => ({ + observe: vi.fn(), + unobserve: vi.fn(), + disconnect: vi.fn(), +})) + +const createComponent = async (props = {}) => { + const element = new Table(props); + document.body.appendChild(element); + await element.updateComplete; + return element; +} + +const itemSchema = { + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + aliases: { type: 'array', items: { type: 'string' } } + }, + required: ['name'] +} + +const schema = { + type: 'array', + items: itemSchema +} + +describe('Table component', () => { + + + test('should render the table', async () => { + const element = await createComponent(); + const div = element.querySelector('table'); + expect(div).toBeTruthy(); + element.remove() + }); + + test('should set schema and update columns', async () => { + const element = await createComponent({ schema }); + + const colHeaders = element.colHeaders; + expect(colHeaders).toEqual(['name', 'age', 'aliases']); + element.remove() + }); + + test('should add rows and validate data', async () => { + const element = await createComponent({ schema }); + + const row = { name: 'John Doe', age: 30, aliases: ['Johny', 'Doe'] } + element.data = [row]; + await element.updated(); + await sleep(1000); + + const tableData = element.table.getData(); + expect(tableData).toEqual([ Object.values(row) ]); + element.remove() + }); + + test('should work with key column', async () => { + const element = await createComponent({ schema, keyColumn: 'name' }); + + const key = 'John Doe'; + const row = { age: 30, aliases: ['Johny', 'Doe'] } + const data = { [key]: row }; + element.data = data; + await element.updated(); + await sleep(1000); + + const tableData = element.table.getData(); + expect(tableData).toEqual([ [key, ...Object.values(row)] ]); + element.remove() + }) + + test('should sort table columns correctly', async () => { + const schema = { + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + email: { type: 'string' }, + }, + required: ['name'] + }; + + const sortedColumns = sortTable(schema, 'name', ['age', 'email']); + expect(sortedColumns).toEqual(['name', 'age', 'email']); + }); +}); \ No newline at end of file From b92a1da5aa6490ef463bd0d8ad9bb27270f6bc8b Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Fri, 14 Jun 2024 15:00:15 -0700 Subject: [PATCH 2/3] Exclude to get up to 70% --- vite.config.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vite.config.js b/vite.config.js index 5667e299b..3bf1b90db 100644 --- a/vite.config.js +++ b/vite.config.js @@ -54,11 +54,16 @@ export default defineConfig({ // Just rendering "src/electron/frontend/core/components/CodeBlock.js", + // Depends on server communication + "src/electron/frontend/core/components/NWBFilePreview.js", + // Unclear how to test "src/electron/frontend/utils/popups.ts", "src/electron/frontend/utils/download.ts", "src/electron/frontend/utils/upload.ts", "src/electron/frontend/core/components/FileSystemSelector.js", // Uses Electron dialog + "src/electron/frontend/core/components/DandiResults.js", // Needs DANDI API Key and network access (unless possibly with a static mocked response) + ], }, }, From 6c2c675f7e8c12c59678f0e390b03783b2bb411d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 14 Jun 2024 22:04:09 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/electron/frontend/core/components/BasicTable.js | 3 +-- src/electron/frontend/core/components/SimpleTable.js | 4 ++-- src/electron/frontend/core/components/Table.js | 11 ++++------- tests/components/table.test.ts | 4 ++-- vite.config.js | 1 - 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/electron/frontend/core/components/BasicTable.js b/src/electron/frontend/core/components/BasicTable.js index 3ac933bc9..acfbbce2b 100644 --- a/src/electron/frontend/core/components/BasicTable.js +++ b/src/electron/frontend/core/components/BasicTable.js @@ -450,10 +450,9 @@ export class BasicTable extends LitElement { for (let key in this.ignore) delete entries[key]; for (let key in this.ignore["*"] ?? {}) delete entries[key]; - const schemaOrder = this.#itemSchema.order ?? [] + const schemaOrder = this.#itemSchema.order ?? []; const order = this.keyColumn ? [this.keyColumn, ...schemaOrder] : schemaOrder; - // Sort Columns by Key Column and Requirement const keys = (this.#keys = diff --git a/src/electron/frontend/core/components/SimpleTable.js b/src/electron/frontend/core/components/SimpleTable.js index 2ad81d520..4ec4ef49b 100644 --- a/src/electron/frontend/core/components/SimpleTable.js +++ b/src/electron/frontend/core/components/SimpleTable.js @@ -968,9 +968,9 @@ export class SimpleTable extends LitElement { for (let key in this.ignore) delete entries[key]; for (let key in this.ignore["*"] ?? {}) delete entries[key]; - const schemaOrder = this.#itemSchema.order ?? [] + const schemaOrder = this.#itemSchema.order ?? []; const order = this.keyColumn ? [this.keyColumn, ...schemaOrder] : schemaOrder; - if (!this.keyColumn && !order.includes('name')) order.unshift('name'); + if (!this.keyColumn && !order.includes("name")) order.unshift("name"); // Sort Columns by Key Column and Requirement this.colHeaders = sortTable( diff --git a/src/electron/frontend/core/components/Table.js b/src/electron/frontend/core/components/Table.js index b1ebd5bcd..3882fea2a 100644 --- a/src/electron/frontend/core/components/Table.js +++ b/src/electron/frontend/core/components/Table.js @@ -12,7 +12,7 @@ const rowSymbol = Symbol("row"); const maxRows = 20; -const isRequired = (col, schema) => schema.required?.includes(col) +const isRequired = (col, schema) => schema.required?.includes(col); export const getEditable = (value, rowData = {}, config, colName) => { if (typeof config === "boolean") return config; @@ -21,7 +21,6 @@ export const getEditable = (value, rowData = {}, config, colName) => { }; export function sortTable(schema, order = []) { - const cols = Object.keys(schema.properties) //Sort alphabetically @@ -37,17 +36,15 @@ export function sortTable(schema, order = []) { if (aRequired) return -1; if (bRequired) return 1; return 0; - }) - + }); - return cols.sort((a, b) => { const idxA = order.indexOf(a); const idxB = order.indexOf(b); if (idxA === -1) return 1; if (idxB === -1) return -1; return idxA - idxB; - }) + }); } // Inject scoped stylesheet @@ -257,7 +254,7 @@ export class Table extends LitElement { } // Sort Columns by Key Column and Requirement - const order = this.keyColumn ? [this.keyColumn, ...this.#itemSchema.order ?? []] : this.#itemSchema.order; + const order = this.keyColumn ? [this.keyColumn, ...(this.#itemSchema.order ?? [])] : this.#itemSchema.order; const colHeaders = (this.colHeaders = sortTable( { diff --git a/tests/components/table.test.ts b/tests/components/table.test.ts index 5f77b4b7e..b281e9f01 100644 --- a/tests/components/table.test.ts +++ b/tests/components/table.test.ts @@ -65,7 +65,7 @@ describe('Table component', () => { const element = await createComponent({ schema, keyColumn: 'name' }); const key = 'John Doe'; - const row = { age: 30, aliases: ['Johny', 'Doe'] } + const row = { age: 30, aliases: ['Johny', 'Doe'] } const data = { [key]: row }; element.data = data; await element.updated(); @@ -89,4 +89,4 @@ describe('Table component', () => { const sortedColumns = sortTable(schema, 'name', ['age', 'email']); expect(sortedColumns).toEqual(['name', 'age', 'email']); }); -}); \ No newline at end of file +}); diff --git a/vite.config.js b/vite.config.js index 3bf1b90db..8eaacbb51 100644 --- a/vite.config.js +++ b/vite.config.js @@ -63,7 +63,6 @@ export default defineConfig({ "src/electron/frontend/utils/upload.ts", "src/electron/frontend/core/components/FileSystemSelector.js", // Uses Electron dialog "src/electron/frontend/core/components/DandiResults.js", // Needs DANDI API Key and network access (unless possibly with a static mocked response) - ], }, },