Test coverage added for the component. Minor refactor
This commit is contained in:
parent
7303aaebda
commit
a39accdb42
|
@ -8,7 +8,8 @@
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import download from "downloadjs"
|
import download from "downloadjs"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { Constants, LuceneUtils } from "@budibase/frontend-core"
|
import { LuceneUtils } from "@budibase/frontend-core"
|
||||||
|
import { utils } from "@budibase/shared-core"
|
||||||
import { ROW_EXPORT_FORMATS } from "constants/backend"
|
import { ROW_EXPORT_FORMATS } from "constants/backend"
|
||||||
|
|
||||||
export let view
|
export let view
|
||||||
|
@ -17,8 +18,6 @@
|
||||||
export let selectedRows = []
|
export let selectedRows = []
|
||||||
export let formats
|
export let formats
|
||||||
|
|
||||||
$: appliedFilters = filters?.filter(filter => !filter.onEmptyFilter)
|
|
||||||
|
|
||||||
const FORMATS = [
|
const FORMATS = [
|
||||||
{
|
{
|
||||||
name: "CSV",
|
name: "CSV",
|
||||||
|
@ -34,6 +33,8 @@
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
$: appliedFilters = filters?.filter(filter => !filter.onEmptyFilter)
|
||||||
|
|
||||||
$: options = FORMATS.filter(format => {
|
$: options = FORMATS.filter(format => {
|
||||||
if (formats && !formats.includes(format.key)) {
|
if (formats && !formats.includes(format.key)) {
|
||||||
return false
|
return false
|
||||||
|
@ -55,14 +56,7 @@
|
||||||
appliedFilters
|
appliedFilters
|
||||||
)
|
)
|
||||||
|
|
||||||
const buildFilterLookup = () => {
|
filterLookup = utils.filterValueToLabel()
|
||||||
return Object.keys(Constants.OperatorOptions).reduce((acc, key) => {
|
|
||||||
const op = Constants.OperatorOptions[key]
|
|
||||||
acc[op.value] = op.label
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
filterLookup = buildFilterLookup()
|
|
||||||
|
|
||||||
const filterDisplay = () => {
|
const filterDisplay = () => {
|
||||||
if (!appliedFilters) {
|
if (!appliedFilters) {
|
||||||
|
@ -173,19 +167,23 @@
|
||||||
>
|
>
|
||||||
{#if selectedRows?.length}
|
{#if selectedRows?.length}
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
<strong>{selectedRows?.length}</strong>
|
<span data-testid="exporting-n-rows">
|
||||||
{`row${selectedRows?.length > 1 ? "s" : ""} will be exported`}
|
<strong>{selectedRows?.length}</strong>
|
||||||
|
{`row${selectedRows?.length > 1 ? "s" : ""} will be exported`}
|
||||||
|
</span>
|
||||||
</Body>
|
</Body>
|
||||||
{:else if appliedFilters?.length || (sorting?.sortOrder && sorting?.sortColumn)}
|
{:else if appliedFilters?.length || (sorting?.sortOrder && sorting?.sortColumn)}
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
{#if !appliedFilters}
|
{#if !appliedFilters}
|
||||||
Exporting <strong>all</strong> rows
|
<span data-testid="exporting-rows">
|
||||||
|
Exporting <strong>all</strong> rows
|
||||||
|
</span>
|
||||||
{:else}
|
{:else}
|
||||||
Filters applied
|
<span data-testid="filters-applied">Filters applied</span>
|
||||||
{/if}
|
{/if}
|
||||||
</Body>
|
</Body>
|
||||||
|
|
||||||
<div class="table-wrap">
|
<div class="table-wrap" data-testid="export-config-table">
|
||||||
<Table
|
<Table
|
||||||
schema={displaySchema}
|
schema={displaySchema}
|
||||||
data={exportOpDisplay}
|
data={exportOpDisplay}
|
||||||
|
@ -202,18 +200,21 @@
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
Exporting <strong>all</strong> rows
|
<span data-testid="export-all-rows">
|
||||||
|
Exporting <strong>all</strong> rows
|
||||||
|
</span>
|
||||||
</Body>
|
</Body>
|
||||||
{/if}
|
{/if}
|
||||||
|
<span data-testid="format-select">
|
||||||
<Select
|
<Select
|
||||||
label="Format"
|
label="Format"
|
||||||
bind:value={exportFormat}
|
bind:value={exportFormat}
|
||||||
{options}
|
{options}
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
getOptionLabel={x => x.name}
|
getOptionLabel={x => x.name}
|
||||||
getOptionValue={x => x.key}
|
getOptionValue={x => x.key}
|
||||||
/>
|
/>
|
||||||
|
</span>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -0,0 +1,240 @@
|
||||||
|
import { it, expect, describe } from "vitest"
|
||||||
|
import { render, screen } from "@testing-library/svelte"
|
||||||
|
import "@testing-library/jest-dom"
|
||||||
|
|
||||||
|
import ExportModal from "./ExportModal.svelte"
|
||||||
|
import { utils } from "@budibase/shared-core"
|
||||||
|
|
||||||
|
const labelLookup = utils.filterValueToLabel()
|
||||||
|
|
||||||
|
const rowText = filter => {
|
||||||
|
let readableField = filter.field.split(":")[1]
|
||||||
|
let rowLabel = labelLookup[filter.operator]
|
||||||
|
let value = Array.isArray(filter.value)
|
||||||
|
? JSON.stringify(filter.value)
|
||||||
|
: filter.value
|
||||||
|
return `${readableField}${rowLabel}${value}`.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultFilters = [
|
||||||
|
{
|
||||||
|
onEmptyFilter: "all",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
vi.mock("svelte", async () => {
|
||||||
|
return {
|
||||||
|
getContext: () => {
|
||||||
|
return {
|
||||||
|
hide: vi.fn(),
|
||||||
|
cancel: vi.fn(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createEventDispatcher: vi.fn(),
|
||||||
|
onDestroy: vi.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
vi.mock("api", async () => {
|
||||||
|
return {
|
||||||
|
API: {
|
||||||
|
exportView: vi.fn(),
|
||||||
|
exportRows: vi.fn(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Export Modal", () => {
|
||||||
|
it("show default messaging with no export config specified", () => {
|
||||||
|
render(ExportModal, {
|
||||||
|
props: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(screen.getByTestId("export-all-rows")).toBeVisible()
|
||||||
|
expect(screen.getByTestId("export-all-rows")).toHaveTextContent(
|
||||||
|
"Exporting all rows"
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(screen.queryByTestId("export-config-table")).toBe(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("indicate that a filter is being applied to the export", () => {
|
||||||
|
const propsCfg = {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
id: "MOQkMx9p9",
|
||||||
|
field: "1:Cost",
|
||||||
|
operator: "rangeHigh",
|
||||||
|
value: "100",
|
||||||
|
valueType: "Value",
|
||||||
|
type: "number",
|
||||||
|
noValue: false,
|
||||||
|
},
|
||||||
|
...defaultFilters,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
render(ExportModal, {
|
||||||
|
props: propsCfg,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(screen.getByTestId("filters-applied")).toBeVisible()
|
||||||
|
expect(screen.getByTestId("filters-applied").textContent).toBe(
|
||||||
|
"Filters applied"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ele = screen.queryByTestId("export-config-table")
|
||||||
|
expect(ele).toBeVisible()
|
||||||
|
|
||||||
|
const rows = ele.getElementsByClassName("spectrum-Table-row")
|
||||||
|
|
||||||
|
expect(rows.length).toBe(1)
|
||||||
|
let rowTextContent = rowText(propsCfg.filters[0])
|
||||||
|
|
||||||
|
//"CostLess than or equal to100"
|
||||||
|
expect(rows[0].textContent?.trim()).toEqual(rowTextContent)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Show only selected row messaging if rows are supplied", () => {
|
||||||
|
const propsCfg = {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
id: "MOQkMx9p9",
|
||||||
|
field: "1:Cost",
|
||||||
|
operator: "rangeHigh",
|
||||||
|
value: "100",
|
||||||
|
valueType: "Value",
|
||||||
|
type: "number",
|
||||||
|
noValue: false,
|
||||||
|
},
|
||||||
|
...defaultFilters,
|
||||||
|
],
|
||||||
|
sorting: {
|
||||||
|
sortColumn: "Cost",
|
||||||
|
sortOrder: "descending",
|
||||||
|
},
|
||||||
|
selectedRows: [
|
||||||
|
{
|
||||||
|
_id: "ro_ta_bb_expenses_57d5f6fe1b6640d8bb22b15f5eae62cd",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: "ro_ta_bb_expenses_99ce5760a53a430bab4349cd70335a07",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
render(ExportModal, {
|
||||||
|
props: propsCfg,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(screen.queryByTestId("export-config-table")).toBeNull()
|
||||||
|
expect(screen.queryByTestId("filters-applied")).toBeNull()
|
||||||
|
|
||||||
|
expect(screen.queryByTestId("exporting-n-rows")).toBeVisible()
|
||||||
|
expect(screen.queryByTestId("exporting-n-rows").textContent).toEqual(
|
||||||
|
"2 rows will be exported"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Show only the configured sort when no filters are specified", () => {
|
||||||
|
const propsCfg = {
|
||||||
|
filters: [...defaultFilters],
|
||||||
|
sorting: {
|
||||||
|
sortColumn: "Cost",
|
||||||
|
sortOrder: "descending",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
render(ExportModal, {
|
||||||
|
props: propsCfg,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(screen.queryByTestId("export-config-table")).toBeVisible()
|
||||||
|
const ele = screen.queryByTestId("export-config-table")
|
||||||
|
const rows = ele.getElementsByClassName("spectrum-Table-row")
|
||||||
|
|
||||||
|
expect(rows.length).toBe(1)
|
||||||
|
expect(rows[0].textContent?.trim()).toEqual(
|
||||||
|
`${propsCfg.sorting.sortColumn}Order By${propsCfg.sorting.sortOrder}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Display all currently configured filters and applied sort", () => {
|
||||||
|
const propsCfg = {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
id: "MOQkMx9p9",
|
||||||
|
field: "1:Cost",
|
||||||
|
operator: "rangeHigh",
|
||||||
|
value: "100",
|
||||||
|
valueType: "Value",
|
||||||
|
type: "number",
|
||||||
|
noValue: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2ot-aB0gE",
|
||||||
|
field: "2:Expense Tags",
|
||||||
|
operator: "contains",
|
||||||
|
value: ["Equipment", "Services"],
|
||||||
|
valueType: "Value",
|
||||||
|
type: "array",
|
||||||
|
noValue: false,
|
||||||
|
},
|
||||||
|
...defaultFilters,
|
||||||
|
],
|
||||||
|
sorting: {
|
||||||
|
sortColumn: "Payment Due",
|
||||||
|
sortOrder: "ascending",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
render(ExportModal, {
|
||||||
|
props: propsCfg,
|
||||||
|
})
|
||||||
|
|
||||||
|
const ele = screen.queryByTestId("export-config-table")
|
||||||
|
expect(ele).toBeVisible()
|
||||||
|
|
||||||
|
const rows = ele.getElementsByClassName("spectrum-Table-row")
|
||||||
|
expect(rows.length).toBe(3)
|
||||||
|
|
||||||
|
let rowTextContent1 = rowText(propsCfg.filters[0])
|
||||||
|
expect(rows[0].textContent?.trim()).toEqual(rowTextContent1)
|
||||||
|
|
||||||
|
let rowTextContent2 = rowText(propsCfg.filters[1])
|
||||||
|
expect(rows[1].textContent?.trim()).toEqual(rowTextContent2)
|
||||||
|
|
||||||
|
expect(rows[2].textContent?.trim()).toEqual(
|
||||||
|
`${propsCfg.sorting.sortColumn}Order By${propsCfg.sorting.sortOrder}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("show only the valid, configured download formats", () => {
|
||||||
|
const propsCfg = {
|
||||||
|
formats: ["badger", "json"],
|
||||||
|
}
|
||||||
|
|
||||||
|
render(ExportModal, {
|
||||||
|
props: propsCfg,
|
||||||
|
})
|
||||||
|
|
||||||
|
let ele = screen.getByTestId("format-select")
|
||||||
|
expect(ele).toBeVisible()
|
||||||
|
|
||||||
|
let formatDisplay = ele.getElementsByTagName("button")[0]
|
||||||
|
|
||||||
|
expect(formatDisplay.textContent.trim()).toBe("JSON")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Load the default format config when no explicit formats are configured", () => {
|
||||||
|
render(ExportModal, {
|
||||||
|
props: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
let ele = screen.getByTestId("format-select")
|
||||||
|
expect(ele).toBeVisible()
|
||||||
|
|
||||||
|
let formatDisplay = ele.getElementsByTagName("button")[0]
|
||||||
|
|
||||||
|
expect(formatDisplay.textContent.trim()).toBe("CSV")
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,3 +1,5 @@
|
||||||
|
import * as Constants from "./constants"
|
||||||
|
|
||||||
export function unreachable(
|
export function unreachable(
|
||||||
value: never,
|
value: never,
|
||||||
message = `No such case in exhaustive switch: ${value}`
|
message = `No such case in exhaustive switch: ${value}`
|
||||||
|
@ -43,3 +45,15 @@ export async function parallelForeach<T>(
|
||||||
|
|
||||||
await Promise.all(promises)
|
await Promise.all(promises)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function filterValueToLabel() {
|
||||||
|
return Object.keys(Constants.OperatorOptions).reduce(
|
||||||
|
(acc: { [key: string]: string }, key: string) => {
|
||||||
|
const ops: { [key: string]: any } = Constants.OperatorOptions
|
||||||
|
const op: { [key: string]: string } = ops[key]
|
||||||
|
acc[op["value"]] = op.label
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue