mirror of https://github.com/Budibase/budibase.git
nocodelowcodelow-codedockerdocker-composeinternal-projectinternal-toolinternal-toolslow-code-developmentlow-code-development-platformopensourceselfhostedweb-devweb-developmentweb-development-toolswebdevwebdevelopmentworkflow-automationautomationdeveloper-tools
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
416 lines
12 KiB
416 lines
12 KiB
const setup = require("./utilities")
|
|
const { events } = require("@budibase/backend-core")
|
|
|
|
function priceTable() {
|
|
return {
|
|
name: "table",
|
|
type: "table",
|
|
key: "name",
|
|
schema: {
|
|
Price: {
|
|
type: "number",
|
|
constraints: {},
|
|
},
|
|
Category: {
|
|
type: "string",
|
|
constraints: {
|
|
type: "string",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
describe("/views", () => {
|
|
let request = setup.getRequest()
|
|
let config = setup.getConfig()
|
|
let table
|
|
|
|
afterAll(setup.afterAll)
|
|
|
|
beforeEach(async () => {
|
|
await config.init()
|
|
table = await config.createTable(priceTable())
|
|
})
|
|
|
|
const saveView = async (view) => {
|
|
const viewToSave = {
|
|
name: "TestView",
|
|
field: "Price",
|
|
calculation: "stats",
|
|
tableId: table._id,
|
|
...view
|
|
}
|
|
return request
|
|
.post(`/api/views`)
|
|
.send(viewToSave)
|
|
.set(config.defaultHeaders())
|
|
.expect("Content-Type", /json/)
|
|
.expect(200)
|
|
}
|
|
|
|
describe("create", () => {
|
|
|
|
it("returns a success message when the view is successfully created", async () => {
|
|
const res = await saveView()
|
|
expect(res.body.tableId).toBe(table._id)
|
|
expect(events.view.created).toBeCalledTimes(1)
|
|
})
|
|
|
|
it("creates a view with a calculation", async () => {
|
|
jest.clearAllMocks()
|
|
|
|
const res = await saveView({ calculation: "count" })
|
|
|
|
expect(res.body.tableId).toBe(table._id)
|
|
expect(events.view.created).toBeCalledTimes(1)
|
|
expect(events.view.updated).not.toBeCalled()
|
|
expect(events.view.calculationCreated).toBeCalledTimes(1)
|
|
expect(events.view.calculationUpdated).not.toBeCalled()
|
|
expect(events.view.calculationDeleted).not.toBeCalled()
|
|
expect(events.view.filterCreated).not.toBeCalled()
|
|
expect(events.view.filterUpdated).not.toBeCalled()
|
|
expect(events.view.filterDeleted).not.toBeCalled()
|
|
})
|
|
|
|
it("creates a view with a filter", async () => {
|
|
jest.clearAllMocks()
|
|
|
|
const res = await saveView({
|
|
calculation: null,
|
|
filters: [{
|
|
value: "1",
|
|
condition: "EQUALS",
|
|
key: "price"
|
|
}],
|
|
})
|
|
|
|
expect(res.body.tableId).toBe(table._id)
|
|
expect(events.view.created).toBeCalledTimes(1)
|
|
expect(events.view.updated).not.toBeCalled()
|
|
expect(events.view.calculationCreated).not.toBeCalled()
|
|
expect(events.view.calculationUpdated).not.toBeCalled()
|
|
expect(events.view.calculationDeleted).not.toBeCalled()
|
|
expect(events.view.filterCreated).toBeCalledTimes(1)
|
|
expect(events.view.filterUpdated).not.toBeCalled()
|
|
expect(events.view.filterDeleted).not.toBeCalled()
|
|
})
|
|
|
|
it("updates the table row with the new view metadata", async () => {
|
|
const res = await request
|
|
.post(`/api/views`)
|
|
.send({
|
|
name: "TestView",
|
|
field: "Price",
|
|
calculation: "stats",
|
|
tableId: table._id,
|
|
})
|
|
.set(config.defaultHeaders())
|
|
.expect("Content-Type", /json/)
|
|
.expect(200)
|
|
expect(res.body.tableId).toBe(table._id)
|
|
|
|
const updatedTable = await config.getTable(table._id)
|
|
expect(updatedTable.views).toEqual({
|
|
TestView: {
|
|
field: "Price",
|
|
calculation: "stats",
|
|
tableId: table._id,
|
|
filters: [],
|
|
schema: {
|
|
sum: {
|
|
type: "number",
|
|
},
|
|
min: {
|
|
type: "number",
|
|
},
|
|
max: {
|
|
type: "number",
|
|
},
|
|
count: {
|
|
type: "number",
|
|
},
|
|
sumsqr: {
|
|
type: "number",
|
|
},
|
|
avg: {
|
|
type: "number",
|
|
},
|
|
field: {
|
|
type: "string",
|
|
},
|
|
},
|
|
},
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("update", () => {
|
|
it("updates a view with no calculation or filter changed", async () => {
|
|
await saveView()
|
|
jest.clearAllMocks()
|
|
|
|
await saveView()
|
|
|
|
expect(events.view.created).not.toBeCalled()
|
|
expect(events.view.updated).toBeCalledTimes(1)
|
|
expect(events.view.calculationCreated).not.toBeCalled()
|
|
expect(events.view.calculationUpdated).not.toBeCalled()
|
|
expect(events.view.calculationDeleted).not.toBeCalled()
|
|
expect(events.view.filterCreated).not.toBeCalled()
|
|
expect(events.view.filterUpdated).not.toBeCalled()
|
|
expect(events.view.filterDeleted).not.toBeCalled()
|
|
})
|
|
|
|
it("updates a view calculation", async () => {
|
|
await saveView({ calculation: "sum" })
|
|
jest.clearAllMocks()
|
|
|
|
await saveView({ calculation: "count" })
|
|
|
|
expect(events.view.created).not.toBeCalled()
|
|
expect(events.view.updated).toBeCalledTimes(1)
|
|
expect(events.view.calculationCreated).not.toBeCalled()
|
|
expect(events.view.calculationUpdated).toBeCalledTimes(1)
|
|
expect(events.view.calculationDeleted).not.toBeCalled()
|
|
expect(events.view.filterCreated).not.toBeCalled()
|
|
expect(events.view.filterUpdated).not.toBeCalled()
|
|
expect(events.view.filterDeleted).not.toBeCalled()
|
|
})
|
|
|
|
it("deletes a view calculation", async () => {
|
|
await saveView({ calculation: "sum" })
|
|
jest.clearAllMocks()
|
|
|
|
await saveView({ calculation: null })
|
|
|
|
expect(events.view.created).not.toBeCalled()
|
|
expect(events.view.updated).toBeCalledTimes(1)
|
|
expect(events.view.calculationCreated).not.toBeCalled()
|
|
expect(events.view.calculationUpdated).not.toBeCalled()
|
|
expect(events.view.calculationDeleted).toBeCalledTimes(1)
|
|
expect(events.view.filterCreated).not.toBeCalled()
|
|
expect(events.view.filterUpdated).not.toBeCalled()
|
|
expect(events.view.filterDeleted).not.toBeCalled()
|
|
})
|
|
|
|
it("updates a view filter", async () => {
|
|
await saveView({ filters: [{
|
|
value: "1",
|
|
condition: "EQUALS",
|
|
key: "price"
|
|
}] })
|
|
jest.clearAllMocks()
|
|
|
|
await saveView({ filters: [{
|
|
value: "2",
|
|
condition: "EQUALS",
|
|
key: "price"
|
|
}] })
|
|
|
|
expect(events.view.created).not.toBeCalled()
|
|
expect(events.view.updated).toBeCalledTimes(1)
|
|
expect(events.view.calculationCreated).not.toBeCalled()
|
|
expect(events.view.calculationUpdated).not.toBeCalled()
|
|
expect(events.view.calculationDeleted).not.toBeCalled()
|
|
expect(events.view.filterCreated).not.toBeCalled()
|
|
expect(events.view.filterUpdated).toBeCalledTimes(1)
|
|
expect(events.view.filterDeleted).not.toBeCalled()
|
|
})
|
|
|
|
it("deletes a view filter", async () => {
|
|
await saveView({ filters: [{
|
|
value: "1",
|
|
condition: "EQUALS",
|
|
key: "price"
|
|
}] })
|
|
jest.clearAllMocks()
|
|
|
|
await saveView({ filters: [] })
|
|
|
|
expect(events.view.created).not.toBeCalled()
|
|
expect(events.view.updated).toBeCalledTimes(1)
|
|
expect(events.view.calculationCreated).not.toBeCalled()
|
|
expect(events.view.calculationUpdated).not.toBeCalled()
|
|
expect(events.view.calculationDeleted).not.toBeCalled()
|
|
expect(events.view.filterCreated).not.toBeCalled()
|
|
expect(events.view.filterUpdated).not.toBeCalled()
|
|
expect(events.view.filterDeleted).toBeCalledTimes(1)
|
|
})
|
|
})
|
|
|
|
describe("fetch", () => {
|
|
beforeEach(async () => {
|
|
table = await config.createTable(priceTable())
|
|
})
|
|
|
|
it("returns only custom views", async () => {
|
|
await config.createView({
|
|
name: "TestView",
|
|
field: "Price",
|
|
calculation: "stats",
|
|
tableId: table._id,
|
|
})
|
|
const res = await request
|
|
.get(`/api/views`)
|
|
.set(config.defaultHeaders())
|
|
.expect("Content-Type", /json/)
|
|
.expect(200)
|
|
expect(res.body.length).toBe(1)
|
|
expect(res.body.find(({ name }) => name === "TestView")).toBeDefined()
|
|
})
|
|
})
|
|
|
|
describe("query", () => {
|
|
it("returns data for the created view", async () => {
|
|
await config.createView({
|
|
name: "TestView",
|
|
field: "Price",
|
|
calculation: "stats",
|
|
tableId: table._id,
|
|
})
|
|
await config.createRow({
|
|
tableId: table._id,
|
|
Price: 1000,
|
|
})
|
|
await config.createRow({
|
|
tableId: table._id,
|
|
Price: 2000,
|
|
})
|
|
await config.createRow({
|
|
tableId: table._id,
|
|
Price: 4000,
|
|
})
|
|
const res = await request
|
|
.get(`/api/views/TestView?calculation=stats`)
|
|
.set(config.defaultHeaders())
|
|
.expect("Content-Type", /json/)
|
|
.expect(200)
|
|
expect(res.body.length).toBe(1)
|
|
expect(res.body).toMatchSnapshot()
|
|
})
|
|
|
|
it("returns data for the created view using a group by", async () => {
|
|
await config.createView({
|
|
calculation: "stats",
|
|
name: "TestView",
|
|
field: "Price",
|
|
groupBy: "Category",
|
|
tableId: table._id,
|
|
})
|
|
await config.createRow({
|
|
tableId: table._id,
|
|
Price: 1000,
|
|
Category: "One",
|
|
})
|
|
await config.createRow({
|
|
tableId: table._id,
|
|
Price: 2000,
|
|
Category: "One",
|
|
})
|
|
await config.createRow({
|
|
tableId: table._id,
|
|
Price: 4000,
|
|
Category: "Two",
|
|
})
|
|
const res = await request
|
|
.get(`/api/views/TestView?calculation=stats&group=Category`)
|
|
.set(config.defaultHeaders())
|
|
.expect("Content-Type", /json/)
|
|
.expect(200)
|
|
|
|
expect(res.body.length).toBe(2)
|
|
expect(res.body).toMatchSnapshot()
|
|
})
|
|
})
|
|
|
|
describe("destroy", () => {
|
|
it("should be able to delete a view", async () => {
|
|
const table = await config.createTable(priceTable())
|
|
const view = await config.createView()
|
|
const res = await request
|
|
.delete(`/api/views/${view.name}`)
|
|
.set(config.defaultHeaders())
|
|
.expect("Content-Type", /json/)
|
|
.expect(200)
|
|
expect(res.body.map).toBeDefined()
|
|
expect(res.body.meta.tableId).toEqual(table._id)
|
|
expect(events.view.deleted).toBeCalledTimes(1)
|
|
})
|
|
})
|
|
|
|
describe("exportView", () => {
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks()
|
|
})
|
|
|
|
const setupExport = async () => {
|
|
const table = await config.createTable()
|
|
await config.createRow({ name: "test-name", description: "test-desc" })
|
|
return table
|
|
}
|
|
|
|
const exportView = async (viewName, format) => {
|
|
return request
|
|
.get(`/api/views/export?view=${viewName}&format=${format}`)
|
|
.set(config.defaultHeaders())
|
|
.expect(200)
|
|
}
|
|
|
|
const assertJsonExport = (res) => {
|
|
const rows = JSON.parse(res.text)
|
|
expect(rows.length).toBe(1)
|
|
expect(rows[0].name).toBe("test-name")
|
|
expect(rows[0].description).toBe("test-desc")
|
|
}
|
|
|
|
const assertCSVExport = (res) => {
|
|
expect(res.text).toBe("\"name\",\"description\"\n\"test-name\",\"test-desc\"")
|
|
}
|
|
|
|
it("should be able to export a table as JSON", async () => {
|
|
const table = await setupExport()
|
|
|
|
const res = await exportView(table._id, "json")
|
|
|
|
assertJsonExport(res)
|
|
expect(events.table.exported).toBeCalledTimes(1)
|
|
expect(events.table.exported).toBeCalledWith(table, "json")
|
|
})
|
|
|
|
it("should be able to export a table as CSV", async () => {
|
|
const table = await setupExport()
|
|
|
|
const res = await exportView(table._id, "csv")
|
|
|
|
assertCSVExport(res)
|
|
expect(events.table.exported).toBeCalledTimes(1)
|
|
expect(events.table.exported).toBeCalledWith(table, "csv")
|
|
})
|
|
|
|
it("should be able to export a view as JSON", async () => {
|
|
let table = await setupExport()
|
|
const view = await config.createView()
|
|
table = await config.getTable(table._id)
|
|
|
|
let res = await exportView(view.name, "json")
|
|
|
|
assertJsonExport(res)
|
|
expect(events.view.exported).toBeCalledTimes(1)
|
|
expect(events.view.exported).toBeCalledWith(table, "json")
|
|
})
|
|
|
|
it("should be able to export a view as CSV", async () => {
|
|
let table = await setupExport()
|
|
const view = await config.createView()
|
|
table = await config.getTable(table._id)
|
|
|
|
let res = await exportView(view.name, "csv")
|
|
|
|
assertCSVExport(res)
|
|
expect(events.view.exported).toBeCalledTimes(1)
|
|
expect(events.view.exported).toBeCalledWith(table, "csv")
|
|
})
|
|
})
|
|
})
|
|
|