mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-28 16:06:41 +01:00
add addColumn, updateColumn, deleteColumn functions + tests
This commit is contained in:
@@ -54,7 +54,7 @@
|
||||
"start": "cross-env PORT=7699 craco start",
|
||||
"startWithEmulators": "cross-env PORT=7699 REACT_APP_FIREBASE_EMULATORS=true craco start",
|
||||
"emulators": "firebase emulators:start --only firestore,auth --import ./emulators/ --export-on-exit",
|
||||
"test": "craco test --env ./src/test/custom-jest-env.js",
|
||||
"test": "craco test --env ./src/test/custom-jest-env.js --verbose --detectOpenHandles",
|
||||
"build": "craco build",
|
||||
"analyze": "source-map-explorer ./build/static/js/*.js",
|
||||
"prepare": "husky install",
|
||||
|
||||
@@ -33,8 +33,9 @@ export type PublicSettings = Partial<{
|
||||
/** Public settings are visible to unauthenticated users */
|
||||
export const publicSettingsAtom = atom<PublicSettings>({});
|
||||
/** Stores a function that updates public settings */
|
||||
export const updatePublicSettingsAtom =
|
||||
atom<UpdateDocFunction<PublicSettings> | null>(null);
|
||||
export const updatePublicSettingsAtom = atom<
|
||||
UpdateDocFunction<PublicSettings> | undefined
|
||||
>(undefined);
|
||||
|
||||
/** Project settings are visible to authenticated users */
|
||||
export type ProjectSettings = Partial<{
|
||||
@@ -54,8 +55,9 @@ export type ProjectSettings = Partial<{
|
||||
/** Project settings are visible to authenticated users */
|
||||
export const projectSettingsAtom = atom<ProjectSettings>({});
|
||||
/** Stores a function that updates project settings */
|
||||
export const updateProjectSettingsAtom =
|
||||
atom<UpdateDocFunction<ProjectSettings> | null>(null);
|
||||
export const updateProjectSettingsAtom = atom<
|
||||
UpdateDocFunction<ProjectSettings> | undefined
|
||||
>(undefined);
|
||||
|
||||
/** Tables visible to the signed-in user based on roles */
|
||||
export const tablesAtom = atom<TableSettings[]>((get) => {
|
||||
@@ -94,8 +96,8 @@ export const createTableAtom = atom<
|
||||
settings: TableSettings,
|
||||
additionalSettings?: AdditionalTableSettings
|
||||
) => Promise<void>)
|
||||
| null
|
||||
>(null);
|
||||
| undefined
|
||||
>(undefined);
|
||||
|
||||
/**
|
||||
* Minimum amount of table settings required to be passed to updateTable to
|
||||
@@ -112,18 +114,18 @@ export const updateTableAtom = atom<
|
||||
settings: MinimumTableSettings,
|
||||
additionalSettings?: AdditionalTableSettings
|
||||
) => Promise<void>)
|
||||
| null
|
||||
>(null);
|
||||
| undefined
|
||||
>(undefined);
|
||||
|
||||
/** Stores a function to delete a table and its schema doc */
|
||||
export const deleteTableAtom = atom<((id: string) => Promise<void>) | null>(
|
||||
null
|
||||
);
|
||||
export const deleteTableAtom = atom<
|
||||
((id: string) => Promise<void>) | undefined
|
||||
>(undefined);
|
||||
|
||||
/** Stores a function to get a table’s schema doc (without listener) */
|
||||
export const getTableSchemaAtom = atom<
|
||||
((id: string) => Promise<TableSchema>) | null
|
||||
>(null);
|
||||
((id: string) => Promise<TableSchema>) | undefined
|
||||
>(undefined);
|
||||
|
||||
/** Roles used in the project based on table settings */
|
||||
export const rolesAtom = atom((get) =>
|
||||
@@ -140,5 +142,6 @@ export const rolesAtom = atom((get) =>
|
||||
/** User management page: all users */
|
||||
export const allUsersAtom = atom<UserSettings[]>([]);
|
||||
/** Stores a function that updates a user document */
|
||||
export const updateUserAtom =
|
||||
atom<UpdateCollectionFunction<UserSettings> | null>(null);
|
||||
export const updateUserAtom = atom<
|
||||
UpdateCollectionFunction<UserSettings> | undefined
|
||||
>(undefined);
|
||||
|
||||
@@ -126,7 +126,12 @@ export const tableSettingsDialogAtom = atom(
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Store the current ID of the table being edited in tableSettingsDialog
|
||||
* to derive tableSettingsDialogSchemaAtom
|
||||
*/
|
||||
export const tableSettingsDialogIdAtom = atom("");
|
||||
/** Get and store the schema document of the current table being edited */
|
||||
export const tableSettingsDialogSchemaAtom = atom(async (get) => {
|
||||
const tableId = get(tableSettingsDialogIdAtom);
|
||||
const getTableSchema = get(getTableSchemaAtom);
|
||||
|
||||
@@ -34,8 +34,9 @@ export type UserSettings = Partial<{
|
||||
/** User info and settings */
|
||||
export const userSettingsAtom = atom<UserSettings>({});
|
||||
/** Stores a function that updates user settings */
|
||||
export const updateUserSettingsAtom =
|
||||
atom<UpdateDocFunction<UserSettings> | null>(null);
|
||||
export const updateUserSettingsAtom = atom<
|
||||
UpdateDocFunction<UserSettings> | undefined
|
||||
>(undefined);
|
||||
|
||||
/**
|
||||
* Stores which theme is currently active, based on user or OS setting.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { atom } from "jotai";
|
||||
import { uniqBy } from "lodash-es";
|
||||
import { uniqBy, orderBy, findIndex } from "lodash-es";
|
||||
|
||||
import {
|
||||
TableSettings,
|
||||
@@ -7,22 +7,147 @@ import {
|
||||
TableFilter,
|
||||
TableOrder,
|
||||
TableRow,
|
||||
ColumnConfig,
|
||||
} from "@src/types/table";
|
||||
|
||||
/** Root atom from which others are derived */
|
||||
export const tableIdAtom = atom<string | undefined>(undefined);
|
||||
/** Store tableSettings from project settings document */
|
||||
export const tableSettingsAtom = atom<TableSettings | undefined>(undefined);
|
||||
/** Store tableSchema from schema document */
|
||||
export const tableSchemaAtom = atom<TableSchema | undefined>(undefined);
|
||||
/** Store function to update tableSchema */
|
||||
export const updateTableSchemaAtom = atom<
|
||||
((update: Partial<TableSchema>) => Promise<void>) | undefined
|
||||
>(undefined);
|
||||
/** Store the table columns as an ordered array */
|
||||
export const tableColumnsOrderedAtom = atom<ColumnConfig[]>((get) => {
|
||||
const tableSchema = get(tableSchemaAtom);
|
||||
if (!tableSchema || !tableSchema.columns) return [];
|
||||
return orderBy(Object.values(tableSchema?.columns ?? {}), "index");
|
||||
});
|
||||
/** Reducer function to convert from array of columns to columns object */
|
||||
export const tableColumnsReducer = (
|
||||
a: Record<string, ColumnConfig>,
|
||||
c: ColumnConfig,
|
||||
index: number
|
||||
) => {
|
||||
a[c.key] = { ...c, index };
|
||||
return a;
|
||||
};
|
||||
|
||||
/**
|
||||
* Store function to add a column to tableSchema, to the end or by index.
|
||||
* Also fixes any issues with column indexes, so they go from 0 to length - 1
|
||||
* @param config - Column config to add. `config.index` is ignored
|
||||
* @param index - Index to add column at. If undefined, adds to end
|
||||
*/
|
||||
export const addColumnAtom = atom((get) => {
|
||||
const tableColumnsOrdered = [...get(tableColumnsOrderedAtom)];
|
||||
const updateTableSchema = get(updateTableSchemaAtom);
|
||||
if (!updateTableSchema) {
|
||||
return async (config: ColumnConfig, index?: number) => {
|
||||
throw new Error("Cannot update table schema");
|
||||
};
|
||||
}
|
||||
|
||||
return (config: Omit<ColumnConfig, "index">, index?: number) => {
|
||||
// If index is provided, insert at index. Otherwise, append to end
|
||||
tableColumnsOrdered.splice(index ?? tableColumnsOrdered.length, 0, {
|
||||
...config,
|
||||
index: index ?? tableColumnsOrdered.length,
|
||||
} as ColumnConfig);
|
||||
|
||||
// Reduce array into single object with updated indexes
|
||||
const updatedColumns = tableColumnsOrdered.reduce(tableColumnsReducer, {});
|
||||
return updateTableSchema({ columns: updatedColumns });
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Store function to update a column in tableSchema. If not found, throws error.
|
||||
* @param key - Unique key of column to update
|
||||
* @param config - Partial column config to add. `config.index` is ignored
|
||||
* @param index - If passed, reorders the column to the index
|
||||
*/
|
||||
export const updateColumnAtom = atom((get) => {
|
||||
const tableColumnsOrdered = [...get(tableColumnsOrderedAtom)];
|
||||
const updateTableSchema = get(updateTableSchemaAtom);
|
||||
if (!updateTableSchema) {
|
||||
return async (key: string, config: Partial<ColumnConfig>) => {
|
||||
throw new Error("Cannot update table schema");
|
||||
};
|
||||
}
|
||||
|
||||
return (key: string, config: Partial<ColumnConfig>, index?: number) => {
|
||||
const currentIndex = findIndex(tableColumnsOrdered, ["key", key]);
|
||||
if (currentIndex === -1)
|
||||
throw new Error(`Column with key "${key}" not found`);
|
||||
|
||||
// If column is not being reordered, just update the config
|
||||
if (!index) {
|
||||
tableColumnsOrdered[currentIndex] = {
|
||||
...tableColumnsOrdered[currentIndex],
|
||||
...config,
|
||||
index: currentIndex,
|
||||
};
|
||||
}
|
||||
// Otherwise, remove the column from the current position
|
||||
// Then insert it at the new position
|
||||
else {
|
||||
const currentColumn = tableColumnsOrdered.splice(currentIndex, 1)[0];
|
||||
tableColumnsOrdered.splice(index, 0, {
|
||||
...currentColumn,
|
||||
...config,
|
||||
index,
|
||||
});
|
||||
}
|
||||
|
||||
// Reduce array into single object with updated indexes
|
||||
const updatedColumns = tableColumnsOrdered.reduce(tableColumnsReducer, {});
|
||||
return updateTableSchema({ columns: updatedColumns });
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Store function to delete a column in tableSchema
|
||||
* @param key - Unique key of column to delete
|
||||
*/
|
||||
export const deleteColumnAtom = atom((get) => {
|
||||
const tableColumnsOrdered = [...get(tableColumnsOrderedAtom)];
|
||||
const updateTableSchema = get(updateTableSchemaAtom);
|
||||
if (!updateTableSchema) {
|
||||
return async (key: string) => {
|
||||
throw new Error("Cannot update table schema");
|
||||
};
|
||||
}
|
||||
|
||||
return (key: string) => {
|
||||
const updatedColumns = tableColumnsOrdered
|
||||
.filter((c) => c.key !== key)
|
||||
.reduce(tableColumnsReducer, {});
|
||||
|
||||
return updateTableSchema({ columns: updatedColumns });
|
||||
};
|
||||
});
|
||||
|
||||
/** Filters applied to the local view */
|
||||
export const tableFiltersAtom = atom<TableFilter[]>([]);
|
||||
/** Orders applied to the local view */
|
||||
export const tableOrdersAtom = atom<TableOrder[]>([]);
|
||||
/** Latest page in the infinite scroll */
|
||||
export const tablePageAtom = atom(0);
|
||||
|
||||
/** Store rows that are out of order or not ready to be written to the db */
|
||||
export const tableRowsLocalAtom = atom<TableRow[]>([]);
|
||||
/** Store rows from the db listener */
|
||||
export const tableRowsDbAtom = atom<TableRow[]>([]);
|
||||
/** Combine tableRowsLocal and tableRowsDb */
|
||||
export const tableRowsAtom = atom<TableRow[]>((get) =>
|
||||
uniqBy(
|
||||
[...get(tableRowsLocalAtom), ...get(tableRowsDbAtom)],
|
||||
"_rowy_ref.path"
|
||||
)
|
||||
);
|
||||
/** Store loading more state for infinite scroll */
|
||||
export const tableLoadingMoreAtom = atom(false);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect } from "react";
|
||||
import { useAtom, PrimitiveAtom, useSetAtom } from "jotai";
|
||||
import { Scope } from "jotai/core/atom";
|
||||
import { RESET } from "jotai/utils";
|
||||
import {
|
||||
query,
|
||||
collection,
|
||||
@@ -46,7 +45,7 @@ interface IUseFirestoreCollectionWithAtomOptions<T> {
|
||||
/** Optionally disable Suspense */
|
||||
disableSuspense?: boolean;
|
||||
/** Set this atom’s value to a function that updates a document in the collection. If `collectionGroup` is true, you must pass the full path. Uses same scope as `dataScope`. */
|
||||
updateDataAtom?: PrimitiveAtom<UpdateCollectionFunction<T> | null>;
|
||||
updateDataAtom?: PrimitiveAtom<UpdateCollectionFunction<T> | undefined>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,7 +162,7 @@ export function useFirestoreCollectionWithAtom<T = TableRow>(
|
||||
unsubscribe();
|
||||
// If `options?.updateDataAtom` was passed,
|
||||
// reset the atom’s value to prevent writes
|
||||
if (updateDataAtom) setUpdateDataAtom(RESET);
|
||||
if (updateDataAtom) setUpdateDataAtom(undefined);
|
||||
};
|
||||
}, [
|
||||
firebaseDb,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect } from "react";
|
||||
import { useAtom, PrimitiveAtom, useSetAtom } from "jotai";
|
||||
import { Scope } from "jotai/core/atom";
|
||||
import { RESET } from "jotai/utils";
|
||||
import {
|
||||
doc,
|
||||
onSnapshot,
|
||||
@@ -26,7 +25,7 @@ interface IUseFirestoreDocWithAtomOptions<T> {
|
||||
/** Optionally create the document if it doesn’t exist with the following data */
|
||||
createIfNonExistent?: T;
|
||||
/** Set this atom’s value to a function that updates the document. Uses same scope as `dataScope`. */
|
||||
updateDataAtom?: PrimitiveAtom<UpdateDocFunction<T> | null>;
|
||||
updateDataAtom?: PrimitiveAtom<UpdateDocFunction<T> | undefined>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,7 +114,7 @@ export function useFirestoreDocWithAtom<T = TableRow>(
|
||||
unsubscribe();
|
||||
// If `options?.updateDataAtom` was passed,
|
||||
// reset the atom’s value to prevent writes
|
||||
if (updateDataAtom) setUpdateDataAtom(RESET);
|
||||
if (updateDataAtom) setUpdateDataAtom(undefined);
|
||||
};
|
||||
}, [
|
||||
firebaseDb,
|
||||
|
||||
@@ -27,7 +27,6 @@ export const ProjectSourceFirebase = memo(function ProjectSourceFirebase() {
|
||||
// Also sets functions to update those documents.
|
||||
useSettingsDocs();
|
||||
useTableFunctions();
|
||||
console.log("rerender");
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
@@ -38,7 +38,6 @@ export function useTableFunctions() {
|
||||
// Set the createTable function
|
||||
const setCreateTable = useSetAtom(createTableAtom, globalScope);
|
||||
useEffect(() => {
|
||||
console.log("effect firebaseDb");
|
||||
setCreateTable(
|
||||
() =>
|
||||
async (
|
||||
@@ -53,7 +52,6 @@ export function useTableFunctions() {
|
||||
|
||||
// Get latest tables
|
||||
const tables = (await getTables()) || [];
|
||||
console.log("createTable", tables);
|
||||
|
||||
// Get columns from imported table settings or _schemaSource if provided
|
||||
let columns: NonNullable<TableSchema["columns"]> =
|
||||
|
||||
282
src/test/tableAtoms.test.tsx
Normal file
282
src/test/tableAtoms.test.tsx
Normal file
@@ -0,0 +1,282 @@
|
||||
import { renderHook, act } from "@testing-library/react";
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import {
|
||||
tableScope,
|
||||
tableSchemaAtom,
|
||||
updateTableSchemaAtom,
|
||||
addColumnAtom,
|
||||
updateColumnAtom,
|
||||
deleteColumnAtom,
|
||||
} from "@src/atoms/tableScope";
|
||||
import { TableSchema } from "@src/types/table";
|
||||
|
||||
import { FieldType } from "@src/constants/fields";
|
||||
|
||||
const initUpdateTableSchemaAtom = (initialTableSchema?: TableSchema) =>
|
||||
renderHook(() => {
|
||||
const setTableSchema = useSetAtom(tableSchemaAtom, tableScope);
|
||||
setTableSchema(initialTableSchema ?? {});
|
||||
|
||||
const setUpdateTableSchema = useSetAtom(updateTableSchemaAtom, tableScope);
|
||||
setUpdateTableSchema(() => async (update: Partial<TableSchema>) => {
|
||||
setTableSchema(update);
|
||||
});
|
||||
});
|
||||
|
||||
const GENERATED_COLUMNS_LENGTH = 10;
|
||||
const generatedColumns: TableSchema["columns"] = {};
|
||||
for (let i = 0; i < GENERATED_COLUMNS_LENGTH; i++)
|
||||
generatedColumns[`column${i}`] = {
|
||||
key: `column${i}`,
|
||||
fieldName: `column${i}`,
|
||||
name: `Column ${i}`,
|
||||
type: FieldType.shortText,
|
||||
index: i,
|
||||
config: {},
|
||||
};
|
||||
|
||||
describe("addColumn", () => {
|
||||
const columnToAdd = {
|
||||
key: "firstName",
|
||||
fieldName: "firstName",
|
||||
name: "First Name",
|
||||
type: FieldType.shortText,
|
||||
index: 0,
|
||||
config: {},
|
||||
};
|
||||
|
||||
test("adds a column to an empty schema", async () => {
|
||||
initUpdateTableSchemaAtom();
|
||||
const {
|
||||
result: { current: addColumn },
|
||||
} = renderHook(() => useAtomValue(addColumnAtom, tableScope));
|
||||
expect(addColumn).toBeDefined();
|
||||
|
||||
await act(() => addColumn(columnToAdd));
|
||||
|
||||
const {
|
||||
result: { current: tableSchema },
|
||||
} = renderHook(() => useAtomValue(tableSchemaAtom, tableScope));
|
||||
expect(tableSchema?.columns).toHaveProperty("firstName");
|
||||
expect(tableSchema?.columns?.firstName).toStrictEqual(columnToAdd);
|
||||
});
|
||||
|
||||
test("adds a column to the end", async () => {
|
||||
initUpdateTableSchemaAtom({ columns: generatedColumns });
|
||||
const {
|
||||
result: { current: addColumn },
|
||||
} = renderHook(() => useAtomValue(addColumnAtom, tableScope));
|
||||
expect(addColumn).toBeDefined();
|
||||
|
||||
await act(() => addColumn(columnToAdd));
|
||||
|
||||
const {
|
||||
result: { current: tableSchema },
|
||||
} = renderHook(() => useAtomValue(tableSchemaAtom, tableScope));
|
||||
expect(tableSchema?.columns).toHaveProperty("firstName");
|
||||
expect(tableSchema?.columns?.firstName.index).toEqual(
|
||||
GENERATED_COLUMNS_LENGTH
|
||||
);
|
||||
});
|
||||
|
||||
test("adds a column at specified index", async () => {
|
||||
initUpdateTableSchemaAtom({ columns: generatedColumns });
|
||||
const {
|
||||
result: { current: addColumn },
|
||||
} = renderHook(() => useAtomValue(addColumnAtom, tableScope));
|
||||
expect(addColumn).toBeDefined();
|
||||
|
||||
await act(() => addColumn(columnToAdd, 7));
|
||||
|
||||
const {
|
||||
result: { current: tableSchema },
|
||||
} = renderHook(() => useAtomValue(tableSchemaAtom, tableScope));
|
||||
expect(tableSchema?.columns).toHaveProperty("firstName");
|
||||
expect(tableSchema?.columns?.firstName.index).toEqual(7);
|
||||
expect(tableSchema?.columns?.["column7"].index).toEqual(8);
|
||||
expect(tableSchema?.columns?.["column8"].index).toEqual(9);
|
||||
expect(tableSchema?.columns?.["column9"].index).toEqual(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateColumn", () => {
|
||||
test("updates a column without reordering", async () => {
|
||||
initUpdateTableSchemaAtom({ columns: generatedColumns });
|
||||
const {
|
||||
result: { current: updateColumn },
|
||||
} = renderHook(() => useAtomValue(updateColumnAtom, tableScope));
|
||||
expect(updateColumn).toBeDefined();
|
||||
|
||||
await act(() => updateColumn("column7", { name: "Updated column" }));
|
||||
|
||||
const {
|
||||
result: { current: tableSchema },
|
||||
} = renderHook(() => useAtomValue(tableSchemaAtom, tableScope));
|
||||
expect(Object.keys(tableSchema?.columns ?? {})).toHaveLength(
|
||||
GENERATED_COLUMNS_LENGTH
|
||||
);
|
||||
expect(tableSchema?.columns?.column7.name).toEqual("Updated column");
|
||||
|
||||
for (let i = 0; i < GENERATED_COLUMNS_LENGTH; i++) {
|
||||
expect(tableSchema?.columns?.[`column${i}`].index).toEqual(i);
|
||||
}
|
||||
});
|
||||
|
||||
test("updates a column and reorders forwards", async () => {
|
||||
initUpdateTableSchemaAtom({ columns: generatedColumns });
|
||||
const {
|
||||
result: { current: updateColumn },
|
||||
} = renderHook(() => useAtomValue(updateColumnAtom, tableScope));
|
||||
expect(updateColumn).toBeDefined();
|
||||
|
||||
const SOURCE_INDEX = 2;
|
||||
const TARGET_INDEX = 4;
|
||||
await act(() =>
|
||||
updateColumn(
|
||||
`column${SOURCE_INDEX}`,
|
||||
{ name: "Updated column" },
|
||||
TARGET_INDEX
|
||||
)
|
||||
);
|
||||
|
||||
const {
|
||||
result: { current: tableSchema },
|
||||
} = renderHook(() => useAtomValue(tableSchemaAtom, tableScope));
|
||||
expect(Object.keys(tableSchema?.columns ?? {})).toHaveLength(
|
||||
GENERATED_COLUMNS_LENGTH
|
||||
);
|
||||
expect(tableSchema?.columns?.[`column${SOURCE_INDEX}`].name).toEqual(
|
||||
"Updated column"
|
||||
);
|
||||
|
||||
for (let i = 0; i < GENERATED_COLUMNS_LENGTH; i++) {
|
||||
let expectedIndex = i;
|
||||
if (i === SOURCE_INDEX) expectedIndex = TARGET_INDEX;
|
||||
else if (i > SOURCE_INDEX && i <= TARGET_INDEX) expectedIndex = i - 1;
|
||||
|
||||
expect(tableSchema?.columns?.[`column${i}`].index).toEqual(expectedIndex);
|
||||
}
|
||||
});
|
||||
|
||||
test("updates a column and reorders backwards", async () => {
|
||||
initUpdateTableSchemaAtom({ columns: generatedColumns });
|
||||
const {
|
||||
result: { current: updateColumn },
|
||||
} = renderHook(() => useAtomValue(updateColumnAtom, tableScope));
|
||||
expect(updateColumn).toBeDefined();
|
||||
|
||||
const SOURCE_INDEX = 9;
|
||||
const TARGET_INDEX = 3;
|
||||
await act(() =>
|
||||
updateColumn(
|
||||
`column${SOURCE_INDEX}`,
|
||||
{ name: "Updated column" },
|
||||
TARGET_INDEX
|
||||
)
|
||||
);
|
||||
|
||||
const {
|
||||
result: { current: tableSchema },
|
||||
} = renderHook(() => useAtomValue(tableSchemaAtom, tableScope));
|
||||
expect(Object.keys(tableSchema?.columns ?? {})).toHaveLength(
|
||||
GENERATED_COLUMNS_LENGTH
|
||||
);
|
||||
expect(tableSchema?.columns?.[`column${SOURCE_INDEX}`].name).toEqual(
|
||||
"Updated column"
|
||||
);
|
||||
|
||||
for (let i = 0; i < GENERATED_COLUMNS_LENGTH; i++) {
|
||||
let expectedIndex = i;
|
||||
if (i === SOURCE_INDEX) expectedIndex = TARGET_INDEX;
|
||||
else if (i < SOURCE_INDEX && i >= TARGET_INDEX) expectedIndex = i + 1;
|
||||
|
||||
expect(tableSchema?.columns?.[`column${i}`].index).toEqual(expectedIndex);
|
||||
}
|
||||
});
|
||||
|
||||
test("doesn't update a column that doesn't exist", async () => {
|
||||
initUpdateTableSchemaAtom({ columns: generatedColumns });
|
||||
const {
|
||||
result: { current: updateColumn },
|
||||
} = renderHook(() => useAtomValue(updateColumnAtom, tableScope));
|
||||
expect(updateColumn).toBeDefined();
|
||||
|
||||
expect(() => {
|
||||
act(() => updateColumn("nonExistentColumn", {}));
|
||||
}).toThrow(/Column with key .* not found/);
|
||||
|
||||
const {
|
||||
result: { current: tableSchema },
|
||||
} = renderHook(() => useAtomValue(tableSchemaAtom, tableScope));
|
||||
expect(tableSchema?.columns).toStrictEqual(generatedColumns);
|
||||
});
|
||||
|
||||
test("doesn't update empty columns", async () => {
|
||||
initUpdateTableSchemaAtom();
|
||||
const {
|
||||
result: { current: updateColumn },
|
||||
} = renderHook(() => useAtomValue(updateColumnAtom, tableScope));
|
||||
expect(updateColumn).toBeDefined();
|
||||
|
||||
expect(() => {
|
||||
act(() => updateColumn("nonExistentColumn", {}));
|
||||
}).toThrow(/Column with key .* not found/);
|
||||
|
||||
const {
|
||||
result: { current: tableSchema },
|
||||
} = renderHook(() => useAtomValue(tableSchemaAtom, tableScope));
|
||||
expect(Object.keys(tableSchema?.columns ?? {})).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleteColumn", () => {
|
||||
test("deletes a column", async () => {
|
||||
initUpdateTableSchemaAtom({ columns: generatedColumns });
|
||||
const {
|
||||
result: { current: deleteColumn },
|
||||
} = renderHook(() => useAtomValue(deleteColumnAtom, tableScope));
|
||||
expect(deleteColumn).toBeDefined();
|
||||
|
||||
await act(() => deleteColumn("column7"));
|
||||
|
||||
const {
|
||||
result: { current: tableSchema },
|
||||
} = renderHook(() => useAtomValue(tableSchemaAtom, tableScope));
|
||||
expect(tableSchema?.columns).not.toHaveProperty("column7");
|
||||
expect(tableSchema?.columns?.["column8"].index).toEqual(7);
|
||||
expect(tableSchema?.columns?.["column9"].index).toEqual(8);
|
||||
});
|
||||
|
||||
test("doesn't delete a non-existent column", async () => {
|
||||
initUpdateTableSchemaAtom({ columns: generatedColumns });
|
||||
const {
|
||||
result: { current: deleteColumn },
|
||||
} = renderHook(() => useAtomValue(deleteColumnAtom, tableScope));
|
||||
expect(deleteColumn).toBeDefined();
|
||||
|
||||
await act(() => deleteColumn("column72"));
|
||||
|
||||
const {
|
||||
result: { current: tableSchema },
|
||||
} = renderHook(() => useAtomValue(tableSchemaAtom, tableScope));
|
||||
expect(tableSchema?.columns).toHaveProperty("column7");
|
||||
expect(Object.keys(tableSchema?.columns ?? {})).toHaveLength(
|
||||
GENERATED_COLUMNS_LENGTH
|
||||
);
|
||||
});
|
||||
|
||||
test("doesn't delete from empty columns", async () => {
|
||||
initUpdateTableSchemaAtom();
|
||||
const {
|
||||
result: { current: deleteColumn },
|
||||
} = renderHook(() => useAtomValue(deleteColumnAtom, tableScope));
|
||||
expect(deleteColumn).toBeDefined();
|
||||
|
||||
await act(() => deleteColumn("column7"));
|
||||
|
||||
const {
|
||||
result: { current: tableSchema },
|
||||
} = renderHook(() => useAtomValue(tableSchemaAtom, tableScope));
|
||||
expect(Object.keys(tableSchema?.columns ?? {})).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
14
src/types/table.d.ts
vendored
14
src/types/table.d.ts
vendored
@@ -19,6 +19,7 @@ export type TableSettings = {
|
||||
id: string;
|
||||
collection: string;
|
||||
name: string;
|
||||
/** Roles that can see this table in the UI and navigate. Firestore Rules need to be set to give access to the data */
|
||||
roles: string[];
|
||||
|
||||
section: string;
|
||||
@@ -46,15 +47,24 @@ export type TableSchema = {
|
||||
};
|
||||
|
||||
export type ColumnConfig = {
|
||||
fieldName: string;
|
||||
/** Unique key for this column. Currently set to the same as fieldName */
|
||||
key: string;
|
||||
/** Field key/name stored in document */
|
||||
fieldName: string;
|
||||
/** User-facing name */
|
||||
name: string;
|
||||
/** Field type stored in config */
|
||||
type: FieldType;
|
||||
|
||||
/** Column index set by addColumn, updateColumn functions */
|
||||
index: number;
|
||||
|
||||
width?: number;
|
||||
editable?: boolean;
|
||||
|
||||
/** Column-specific config */
|
||||
config: { [key: string]: any };
|
||||
[key: string]: any;
|
||||
// [key: string]: any;
|
||||
};
|
||||
|
||||
export type TableFilter = {
|
||||
|
||||
Reference in New Issue
Block a user