mirror of https://github.com/nocodb/nocodb
Pranav C
1 year ago
committed by
GitHub
27 changed files with 2527 additions and 1473 deletions
@ -0,0 +1,5 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */ |
||||
module.exports = { |
||||
preset: 'ts-jest', |
||||
testEnvironment: 'node', |
||||
}; |
@ -0,0 +1,77 @@
|
||||
import { |
||||
FormulaDataTypes, |
||||
validateFormulaAndExtractTreeWithType, |
||||
} from './formulaHelpers'; |
||||
import UITypes from './UITypes'; |
||||
|
||||
describe('Formula parsing and type validation', () => { |
||||
it('Simple formula', async () => { |
||||
const result = await validateFormulaAndExtractTreeWithType({ |
||||
formula: '1 + 2', |
||||
columns: [], |
||||
clientOrSqlUi: 'mysql2', |
||||
getMeta: async () => ({}), |
||||
}); |
||||
|
||||
expect(result.dataType).toEqual(FormulaDataTypes.NUMERIC); |
||||
}); |
||||
|
||||
it('Formula with IF condition', async () => { |
||||
const result = await validateFormulaAndExtractTreeWithType({ |
||||
formula: 'IF({column}, "Found", BLANK())', |
||||
columns: [ |
||||
{ |
||||
id: 'cid', |
||||
title: 'column', |
||||
uidt: UITypes.Number, |
||||
}, |
||||
], |
||||
clientOrSqlUi: 'mysql2', |
||||
getMeta: async () => ({}), |
||||
}); |
||||
|
||||
expect(result.dataType).toEqual(FormulaDataTypes.STRING); |
||||
}); |
||||
it('Complex formula', async () => { |
||||
const result = await validateFormulaAndExtractTreeWithType({ |
||||
formula: |
||||
'SWITCH({column2},"value1",IF({column1}, "Found", BLANK()),"value2", 2)', |
||||
columns: [ |
||||
{ |
||||
id: 'id1', |
||||
title: 'column1', |
||||
uidt: UITypes.Number, |
||||
}, |
||||
{ |
||||
id: 'id2', |
||||
title: 'column2', |
||||
uidt: UITypes.SingleLineText, |
||||
}, |
||||
], |
||||
clientOrSqlUi: 'mysql2', |
||||
getMeta: async () => ({}), |
||||
}); |
||||
|
||||
expect(result.dataType).toEqual(FormulaDataTypes.STRING); |
||||
|
||||
const result1 = await validateFormulaAndExtractTreeWithType({ |
||||
formula: 'SWITCH({column2},"value1",IF({column1}, 1, 2),"value2", 2)', |
||||
columns: [ |
||||
{ |
||||
id: 'id1', |
||||
title: 'column1', |
||||
uidt: UITypes.Number, |
||||
}, |
||||
{ |
||||
id: 'id2', |
||||
title: 'column2', |
||||
uidt: UITypes.SingleLineText, |
||||
}, |
||||
], |
||||
clientOrSqlUi: 'mysql2', |
||||
getMeta: async () => ({}), |
||||
}); |
||||
|
||||
expect(result1.dataType).toEqual(FormulaDataTypes.NUMERIC); |
||||
}); |
||||
}); |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,16 @@
|
||||
import type { Knex } from 'knex'; |
||||
import { MetaTable } from '~/utils/globals'; |
||||
|
||||
const up = async (knex: Knex) => { |
||||
await knex.schema.alterTable(MetaTable.COL_FORMULA, (table) => { |
||||
table.text('parsed_tree'); |
||||
}); |
||||
}; |
||||
|
||||
const down = async (knex: Knex) => { |
||||
await knex.schema.alterTable(MetaTable.COL_FORMULA, (table) => { |
||||
table.dropColumn('parsed_tree'); |
||||
}); |
||||
}; |
||||
|
||||
export { up, down }; |
@ -0,0 +1,125 @@
|
||||
import 'mocha'; |
||||
import { UITypes } from 'nocodb-sdk'; |
||||
import { expect } from 'chai'; |
||||
import init from '../../init'; |
||||
import { createProject } from '../../factory/base'; |
||||
import { createTable } from '../../factory/table'; |
||||
import { createBulkRows, listRow, rowMixedValue } from '../../factory/row'; |
||||
import { updateColumn } from '../../factory/column'; |
||||
import type Model from '../../../../src/models/Model'; |
||||
import type Base from '~/models/Base'; |
||||
|
||||
let context; |
||||
let base: Base; |
||||
let table: Model; |
||||
let columns: any[]; |
||||
let unfilteredRecords: any[] = []; |
||||
|
||||
function formulaRegExpBased() { |
||||
// prepare data for test cases
|
||||
beforeEach(async function () { |
||||
context = await init(); |
||||
base = await createProject(context); |
||||
table = await createTable(context, base, { |
||||
table_name: 'sampleTable', |
||||
title: 'sampleTable', |
||||
columns: [ |
||||
{ |
||||
column_name: 'Id', |
||||
title: 'Id', |
||||
uidt: UITypes.ID, |
||||
}, |
||||
{ |
||||
column_name: 'Title', |
||||
title: 'Title', |
||||
uidt: UITypes.SingleLineText, |
||||
}, |
||||
{ |
||||
column_name: 'formula', |
||||
title: 'formula', |
||||
uidt: UITypes.Formula, |
||||
formula: '20', |
||||
}, |
||||
], |
||||
}); |
||||
|
||||
columns = await table.getColumns(); |
||||
|
||||
const rowAttributes = []; |
||||
for (let i = 0; i < 100; i++) { |
||||
const row = { |
||||
Title: rowMixedValue(columns[1], i), |
||||
}; |
||||
rowAttributes.push(row); |
||||
} |
||||
|
||||
await createBulkRows(context, { |
||||
base, |
||||
table, |
||||
values: rowAttributes, |
||||
}); |
||||
unfilteredRecords = await listRow({ base, table }); |
||||
|
||||
// verify length of unfiltered records to be 800
|
||||
expect(unfilteredRecords.length).to.equal(100); |
||||
}); |
||||
|
||||
it('Type: REGEX_MATCH ', async () => { |
||||
// if not pg or mysql, skip regex test since it is not implemented for other databases
|
||||
if (!['pg', 'mysql2', 'mysql'].includes(base.sources[0].type)) { |
||||
return; |
||||
} |
||||
|
||||
const formulaList = [ |
||||
`REGEX_MATCH("123-45-6789", "\\d{3}-\\d{2}-\\d{4}")`, |
||||
'REGEX_MATCH("123-45-6789", "\\d{3}-\\d{2}-\\d{4}")', |
||||
'REGEX_MATCH("123-45-6789", "\\d{3}-\\d{2}-\\d{4}")', |
||||
'REGEX_MATCH("ABC-45-6789", "\\w{3}-\\d{2}-\\d{4}")', |
||||
'REGEX_MATCH("123-XY-6789", "\\d{3}-\\D{2}-\\d{4}")', |
||||
'REGEX_MATCH("123-45-$#@!", "123-45-[\\s\\S]{4}")', |
||||
'REGEX_MATCH("123456789", "1?2?3?-?4?5-?6?7?8?9?")', |
||||
'REGEX_MATCH("123-456789", "\\d{3}-?\\d{2}-?\\d{4}")', |
||||
'REGEX_MATCH("123-45-6789", "123-\\d{2}-6789")', |
||||
'REGEX_MATCH("abc123", "[a-z]{3}\\d{3}")', |
||||
'REGEX_MATCH("A1B2C3", "[A-Z]\\d[A-Z]\\d[A-Z]\\d")', |
||||
'REGEX_MATCH("hello123world", "\\w{5}\\d{3}\\w{5}")', |
||||
'REGEX_MATCH("email@example.com", "[a-zA-Z]+@[a-zA-Z]+\\.[a-zA-Z]+")', |
||||
'REGEX_MATCH("2023-12-14", "\\d{4}-\\d{2}-\\d{2}")', |
||||
'REGEX_MATCH("USD 100.50", "USD \\d+\\.\\d{2}")', |
||||
'REGEX_MATCH("http://www.example.com", "https?://[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}")', |
||||
'REGEX_MATCH("555-1234", "\\d{3}-\\d{4}")', |
||||
'REGEX_MATCH("username123", "[a-zA-Z]+\\d{3}")', |
||||
'REGEX_MATCH("apple, orange, banana", "\\w+, \\w+, \\w+")', |
||||
'REGEX_MATCH("aaaabbcc", "(\\w{2})\\1")', |
||||
'REGEX_MATCH("1234567890", "\\d{10}")', |
||||
'REGEX_MATCH("12.34", "\\d+\\.\\d{2}")', |
||||
'REGEX_MATCH("123 Main St, City", "\\d+ [a-zA-Z]+ St, [a-zA-Z]+")', |
||||
'REGEX_MATCH("X1Y2Z3", "[A-Z]\\d[A-Z]\\d[A-Z]\\d")', |
||||
'REGEX_MATCH("555-555-5555", "\\d{3}-\\d{3}-\\d{4}")', |
||||
'REGEX_MATCH("password123", "^(?=.*\\d)(?=.*[a-zA-Z]).{8,}$")', |
||||
'REGEX_MATCH("12345", "^[0-9]{5}$")', |
||||
'REGEX_MATCH("abc123!@#", "[a-zA-Z0-9!@#]+")', |
||||
'REGEX_MATCH("12-December-2023", "\\d{2}-[a-zA-Z]+-\\d{4}")', |
||||
]; |
||||
|
||||
for (let i = 0; i < formulaList.length; i++) { |
||||
await updateColumn(context, { |
||||
table, |
||||
column: columns[2], |
||||
attr: { |
||||
formula: formulaList[i], |
||||
formula_raw: formulaList[i], |
||||
title: 'formula', |
||||
uidt: UITypes.Formula, |
||||
}, |
||||
}); |
||||
|
||||
unfilteredRecords = await listRow({ base, table }); |
||||
expect(unfilteredRecords[0].formula).to.equal(1); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
export default function () { |
||||
describe('Formula: REGEXP based', formulaRegExpBased); |
||||
} |
Loading…
Reference in new issue