TestGrid.tsx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. import React from 'react';
  2. import { FieldType, NumberFormat, NumberTypeOptionPB, SelectOptionCellDataPB, ViewLayoutPB } from '@/services/backend';
  3. import { Log } from '$app/utils/log';
  4. import {
  5. assert,
  6. assertFieldName,
  7. assertNumberOfFields,
  8. assertNumberOfRows,
  9. assertTextCell,
  10. createSingleSelectOptions,
  11. createTestDatabaseView,
  12. editTextCell,
  13. findFirstFieldInfoWithFieldType,
  14. makeCheckboxCellController,
  15. makeDateCellController,
  16. makeMultiSelectCellController,
  17. makeSingleSelectCellController,
  18. makeTextCellController,
  19. makeURLCellController,
  20. openTestDatabase,
  21. } from './DatabaseTestHelper';
  22. import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
  23. import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
  24. import { None, Some } from 'ts-results';
  25. import { RowBackendService } from '$app/stores/effects/database/row/row_bd_svc';
  26. import { makeNumberTypeOptionContext } from '$app/stores/effects/database/field/type_option/type_option_context';
  27. import { CalendarData } from '$app/stores/effects/database/cell/controller_builder';
  28. import { DatabaseEventMoveField } from '@/services/backend/events/flowy-database';
  29. export const RunAllGridTests = () => {
  30. async function run() {
  31. await createBuildInGrid();
  32. await testEditGridCell();
  33. await testCreateRow();
  34. await testDeleteRow();
  35. await testCreateOptionInCell();
  36. await testGetSingleSelectFieldData();
  37. await testSwitchFromSingleSelectToNumber();
  38. await testSwitchFromMultiSelectToRichText();
  39. await testEditField();
  40. await testCreateNewField();
  41. await testDeleteField();
  42. }
  43. return (
  44. <React.Fragment>
  45. <div>
  46. <button className='rounded-md bg-red-400 p-4' type='button' onClick={() => run()}>
  47. Run all grid tests
  48. </button>
  49. </div>
  50. </React.Fragment>
  51. );
  52. };
  53. async function createBuildInGrid() {
  54. const view = await createTestDatabaseView(ViewLayoutPB.Grid);
  55. const databaseController = await openTestDatabase(view.id);
  56. databaseController.subscribe({
  57. onViewChanged: (databasePB) => {
  58. Log.debug('Did receive database:' + databasePB);
  59. },
  60. // onRowsChanged: async (rows) => {
  61. // if (rows.length !== 3) {
  62. // throw Error('Expected number of rows is 3, but receive ' + rows.length);
  63. // }
  64. // },
  65. onFieldsChanged: (fields) => {
  66. if (fields.length !== 3) {
  67. throw Error('Expected number of fields is 3, but receive ' + fields.length);
  68. }
  69. },
  70. });
  71. await databaseController.open().then((result) => result.unwrap());
  72. await databaseController.dispose();
  73. }
  74. async function testEditGridCell() {
  75. const view = await createTestDatabaseView(ViewLayoutPB.Grid);
  76. const databaseController = await openTestDatabase(view.id);
  77. await databaseController.open().then((result) => result.unwrap());
  78. for (const [index, row] of databaseController.databaseViewCache.rowInfos.entries()) {
  79. const cellContent = index.toString();
  80. const fieldInfo = findFirstFieldInfoWithFieldType(row, FieldType.RichText).unwrap();
  81. await editTextCell(fieldInfo.field.id, row, databaseController, cellContent);
  82. await assertTextCell(fieldInfo.field.id, row, databaseController, cellContent);
  83. }
  84. }
  85. async function testEditTextCell() {
  86. const view = await createTestDatabaseView(ViewLayoutPB.Grid);
  87. const databaseController = await openTestDatabase(view.id);
  88. await databaseController.open().then((result) => result.unwrap());
  89. const row = databaseController.databaseViewCache.rowInfos[0];
  90. const textField = findFirstFieldInfoWithFieldType(row, FieldType.RichText).unwrap();
  91. const textCellController = await makeTextCellController(textField.field.id, row, databaseController).then((result) =>
  92. result.unwrap()
  93. );
  94. textCellController.subscribeChanged({
  95. onCellChanged: (content) => {
  96. Log.info('Receive text:', content);
  97. },
  98. });
  99. await textCellController.saveCellData('hello react');
  100. await new Promise((resolve) => setTimeout(resolve, 200));
  101. await databaseController.dispose();
  102. }
  103. async function testEditURLCell() {
  104. const view = await createTestDatabaseView(ViewLayoutPB.Grid);
  105. const databaseController = await openTestDatabase(view.id);
  106. await databaseController.open().then((result) => result.unwrap());
  107. const typeOptionController = new TypeOptionController(view.id, None, FieldType.URL);
  108. await typeOptionController.initialize();
  109. const row = databaseController.databaseViewCache.rowInfos[0];
  110. const urlCellController = await makeURLCellController(typeOptionController.fieldId, row, databaseController).then(
  111. (result) => result.unwrap()
  112. );
  113. urlCellController.subscribeChanged({
  114. onCellChanged: (content) => {
  115. const pb = content.unwrap();
  116. Log.info('Receive url data:', pb.url, pb.content);
  117. },
  118. });
  119. await urlCellController.saveCellData('hello react');
  120. await new Promise((resolve) => setTimeout(resolve, 200));
  121. await urlCellController.saveCellData('appflowy.io');
  122. await new Promise((resolve) => setTimeout(resolve, 200));
  123. }
  124. async function testEditDateCell() {
  125. const view = await createTestDatabaseView(ViewLayoutPB.Grid);
  126. const databaseController = await openTestDatabase(view.id);
  127. await databaseController.open().then((result) => result.unwrap());
  128. const typeOptionController = new TypeOptionController(view.id, None, FieldType.DateTime);
  129. await typeOptionController.initialize();
  130. const row = databaseController.databaseViewCache.rowInfos[0];
  131. const dateCellController = await makeDateCellController(typeOptionController.fieldId, row, databaseController).then(
  132. (result) => result.unwrap()
  133. );
  134. dateCellController.subscribeChanged({
  135. onCellChanged: (content) => {
  136. const pb = content.unwrap();
  137. Log.info('Receive date data:', pb.date, pb.time);
  138. },
  139. });
  140. const date = new CalendarData(new Date(), true, '13:00');
  141. await dateCellController.saveCellData(date);
  142. await new Promise((resolve) => setTimeout(resolve, 200));
  143. }
  144. async function testCheckboxCell() {
  145. const view = await createTestDatabaseView(ViewLayoutPB.Grid);
  146. const databaseController = await openTestDatabase(view.id);
  147. await databaseController.open().then((result) => result.unwrap());
  148. const typeOptionController = new TypeOptionController(view.id, None, FieldType.Checkbox);
  149. await typeOptionController.initialize();
  150. const row = databaseController.databaseViewCache.rowInfos[0];
  151. const checkboxCellController = await makeCheckboxCellController(
  152. typeOptionController.fieldId,
  153. row,
  154. databaseController
  155. ).then((result) => result.unwrap());
  156. checkboxCellController.subscribeChanged({
  157. onCellChanged: (content) => {
  158. const pb = content.unwrap();
  159. Log.info('Receive checkbox data:', pb);
  160. },
  161. });
  162. await checkboxCellController.saveCellData('true');
  163. await new Promise((resolve) => setTimeout(resolve, 200));
  164. }
  165. async function testCreateRow() {
  166. const view = await createTestDatabaseView(ViewLayoutPB.Grid);
  167. const databaseController = await openTestDatabase(view.id);
  168. await databaseController.open().then((result) => result.unwrap());
  169. await assertNumberOfRows(view.id, 3);
  170. // Create a row from a DatabaseController or create using the RowBackendService
  171. await databaseController.createRow();
  172. await assertNumberOfRows(view.id, 4);
  173. await databaseController.dispose();
  174. }
  175. async function testDeleteRow() {
  176. const view = await createTestDatabaseView(ViewLayoutPB.Grid);
  177. const databaseController = await openTestDatabase(view.id);
  178. await databaseController.open().then((result) => result.unwrap());
  179. const rows = databaseController.databaseViewCache.rowInfos;
  180. const svc = new RowBackendService(view.id);
  181. await svc.deleteRow(rows[0].row.id);
  182. await assertNumberOfRows(view.id, 2);
  183. // Wait the databaseViewCache get the change notification and
  184. // update the rows.
  185. await new Promise((resolve) => setTimeout(resolve, 200));
  186. if (databaseController.databaseViewCache.rowInfos.length !== 2) {
  187. throw Error('The number of rows is not match');
  188. }
  189. await databaseController.dispose();
  190. }
  191. async function testCreateOptionInCell() {
  192. const view = await createTestDatabaseView(ViewLayoutPB.Grid);
  193. const databaseController = await openTestDatabase(view.id);
  194. await databaseController.open().then((result) => result.unwrap());
  195. for (const [index, row] of databaseController.databaseViewCache.rowInfos.entries()) {
  196. if (index === 0) {
  197. const fieldInfo = findFirstFieldInfoWithFieldType(row, FieldType.SingleSelect).unwrap();
  198. const cellController = await makeSingleSelectCellController(fieldInfo.field.id, row, databaseController).then(
  199. (result) => result.unwrap()
  200. );
  201. // eslint-disable-next-line @typescript-eslint/await-thenable
  202. await cellController.subscribeChanged({
  203. onCellChanged: (value) => {
  204. if (value.some) {
  205. const option: SelectOptionCellDataPB = value.unwrap();
  206. console.log(option);
  207. }
  208. },
  209. });
  210. const backendSvc = new SelectOptionCellBackendService(cellController.cellIdentifier);
  211. await backendSvc.createOption({ name: 'option' + index });
  212. await cellController.dispose();
  213. }
  214. }
  215. await databaseController.dispose();
  216. }
  217. async function testMoveField() {
  218. const view = await createTestDatabaseView(ViewLayoutPB.Grid);
  219. const databaseController = await openTestDatabase(view.id);
  220. await databaseController.open().then((result) => result.unwrap());
  221. databaseController.subscribe({
  222. onFieldsChanged: (value) => {
  223. Log.info('Receive fields data:', value);
  224. },
  225. });
  226. const fieldInfos = [...databaseController.fieldController.fieldInfos];
  227. const field_id = fieldInfos[0].field.id;
  228. await databaseController.moveField({ fieldId: field_id, fromIndex: 0, toIndex: 1 });
  229. await new Promise((resolve) => setTimeout(resolve, 200));
  230. assert(databaseController.fieldController.fieldInfos[1].field.id === field_id);
  231. }
  232. async function testGetSingleSelectFieldData() {
  233. const view = await createTestDatabaseView(ViewLayoutPB.Grid);
  234. const databaseController = await openTestDatabase(view.id);
  235. await databaseController.open().then((result) => result.unwrap());
  236. // Find the single select column
  237. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  238. const singleSelect = databaseController.fieldController.fieldInfos.find(
  239. (fieldInfo) => fieldInfo.field.field_type === FieldType.SingleSelect
  240. )!;
  241. // Create options
  242. const singleSelectTypeOptionContext = await createSingleSelectOptions(view.id, singleSelect, [
  243. 'Task 1',
  244. 'Task 2',
  245. 'Task 3',
  246. ]);
  247. // Read options
  248. const options = await singleSelectTypeOptionContext.getTypeOption().then((result) => result.unwrap());
  249. console.log(options);
  250. await databaseController.dispose();
  251. }
  252. async function testSwitchFromSingleSelectToNumber() {
  253. const view = await createTestDatabaseView(ViewLayoutPB.Grid);
  254. const databaseController = await openTestDatabase(view.id);
  255. await databaseController.open().then((result) => result.unwrap());
  256. // Find the single select column
  257. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  258. const singleSelect = databaseController.fieldController.fieldInfos.find(
  259. (fieldInfo) => fieldInfo.field.field_type === FieldType.SingleSelect
  260. )!;
  261. const typeOptionController = new TypeOptionController(view.id, Some(singleSelect));
  262. await typeOptionController.switchToField(FieldType.Number);
  263. // Check the number type option
  264. const numberTypeOptionContext = makeNumberTypeOptionContext(typeOptionController);
  265. const numberTypeOption: NumberTypeOptionPB = await numberTypeOptionContext
  266. .getTypeOption()
  267. .then((result) => result.unwrap());
  268. const format: NumberFormat = numberTypeOption.format;
  269. if (format !== NumberFormat.Num) {
  270. throw Error('The default format should be number');
  271. }
  272. await databaseController.dispose();
  273. }
  274. async function testSwitchFromMultiSelectToRichText() {
  275. const view = await createTestDatabaseView(ViewLayoutPB.Grid);
  276. const databaseController = await openTestDatabase(view.id);
  277. await databaseController.open().then((result) => result.unwrap());
  278. // Create multi-select field
  279. const typeOptionController = new TypeOptionController(view.id, None, FieldType.MultiSelect);
  280. await typeOptionController.initialize();
  281. // Insert options to first row
  282. const row = databaseController.databaseViewCache.rowInfos[0];
  283. const multiSelectField = typeOptionController.getFieldInfo();
  284. // const multiSelectField = findFirstFieldInfoWithFieldType(row, FieldType.MultiSelect).unwrap();
  285. const selectOptionCellController = await makeMultiSelectCellController(
  286. multiSelectField.field.id,
  287. row,
  288. databaseController
  289. ).then((result) => result.unwrap());
  290. const backendSvc = new SelectOptionCellBackendService(selectOptionCellController.cellIdentifier);
  291. await backendSvc.createOption({ name: 'A' });
  292. await backendSvc.createOption({ name: 'B' });
  293. await backendSvc.createOption({ name: 'C' });
  294. const selectOptionCellData = await selectOptionCellController.getCellData().then((result) => result.unwrap());
  295. if (selectOptionCellData.options.length !== 3) {
  296. throw Error('The options should equal to 3');
  297. }
  298. if (selectOptionCellData.select_options.length !== 3) {
  299. throw Error('The selected options should equal to 3');
  300. }
  301. await selectOptionCellController.dispose();
  302. // Switch to RichText field type
  303. await typeOptionController.switchToField(FieldType.RichText).then((result) => result.unwrap());
  304. if (typeOptionController.fieldType !== FieldType.RichText) {
  305. throw Error('The field type should be text');
  306. }
  307. const textCellController = await makeTextCellController(multiSelectField.field.id, row, databaseController).then(
  308. (result) => result.unwrap()
  309. );
  310. const cellContent = await textCellController.getCellData();
  311. if (cellContent.unwrap() !== 'A,B,C') {
  312. throw Error('The cell content should be A,B,C, but receive: ' + cellContent.unwrap());
  313. }
  314. await databaseController.dispose();
  315. }
  316. async function testEditField() {
  317. const view = await createTestDatabaseView(ViewLayoutPB.Grid);
  318. const databaseController = await openTestDatabase(view.id);
  319. await databaseController.open().then((result) => result.unwrap());
  320. const fieldInfos = databaseController.fieldController.fieldInfos;
  321. // Modify the name of the field
  322. const firstFieldInfo = fieldInfos[0];
  323. const controller = new TypeOptionController(view.id, Some(firstFieldInfo));
  324. await controller.initialize();
  325. const newName = 'hello world';
  326. await controller.setFieldName(newName);
  327. await new Promise((resolve) => setTimeout(resolve, 200));
  328. await assertFieldName(view.id, firstFieldInfo.field.id, firstFieldInfo.field.field_type, newName);
  329. await databaseController.dispose();
  330. }
  331. async function testCreateNewField() {
  332. const view = await createTestDatabaseView(ViewLayoutPB.Grid);
  333. const databaseController = await openTestDatabase(view.id);
  334. await databaseController.open().then((result) => result.unwrap());
  335. await assertNumberOfFields(view.id, 3);
  336. // Modify the name of the field
  337. const controller = new TypeOptionController(view.id, None);
  338. await controller.initialize();
  339. await assertNumberOfFields(view.id, 4);
  340. await databaseController.dispose();
  341. }
  342. async function testDeleteField() {
  343. const view = await createTestDatabaseView(ViewLayoutPB.Grid);
  344. const databaseController = await openTestDatabase(view.id);
  345. await databaseController.open().then((result) => result.unwrap());
  346. // Modify the name of the field.
  347. // The fieldInfos[0] is the primary field by default, we can't delete it.
  348. // So let choose the second fieldInfo.
  349. const fieldInfo = databaseController.fieldController.fieldInfos[1];
  350. const controller = new TypeOptionController(view.id, Some(fieldInfo));
  351. await controller.initialize();
  352. await assertNumberOfFields(view.id, 3);
  353. await controller.deleteField();
  354. await assertNumberOfFields(view.id, 2);
  355. await databaseController.dispose();
  356. }
  357. export const TestCreateGrid = () => {
  358. return TestButton('Test create build-in grid', createBuildInGrid);
  359. };
  360. export const TestEditCell = () => {
  361. return TestButton('Test editing cell', testEditGridCell);
  362. };
  363. export const TestEditTextCell = () => {
  364. return TestButton('Test editing text cell', testEditTextCell);
  365. };
  366. export const TestEditURLCell = () => {
  367. return TestButton('Test editing URL cell', testEditURLCell);
  368. };
  369. export const TestEditDateCell = () => {
  370. return TestButton('Test editing date cell', testEditDateCell);
  371. };
  372. export const TestEditCheckboxCell = () => {
  373. return TestButton('Test editing checkbox cell', testCheckboxCell);
  374. };
  375. export const TestCreateRow = () => {
  376. return TestButton('Test create row', testCreateRow);
  377. };
  378. export const TestDeleteRow = () => {
  379. return TestButton('Test delete row', testDeleteRow);
  380. };
  381. export const TestCreateSelectOptionInCell = () => {
  382. return TestButton('Test create a select option in cell', testCreateOptionInCell);
  383. };
  384. export const TestGetSingleSelectFieldData = () => {
  385. return TestButton('Test get single-select column data', testGetSingleSelectFieldData);
  386. };
  387. export const TestSwitchFromSingleSelectToNumber = () => {
  388. return TestButton('Test switch from single-select to number column', testSwitchFromSingleSelectToNumber);
  389. };
  390. export const TestSwitchFromMultiSelectToText = () => {
  391. return TestButton('Test switch from multi-select to text column', testSwitchFromMultiSelectToRichText);
  392. };
  393. export const TestMoveField = () => {
  394. return TestButton('Test move field', testMoveField);
  395. };
  396. export const TestEditField = () => {
  397. return TestButton('Test edit the column name', testEditField);
  398. };
  399. export const TestCreateNewField = () => {
  400. return TestButton('Test create a new column', testCreateNewField);
  401. };
  402. export const TestDeleteField = () => {
  403. return TestButton('Test delete a new column', testDeleteField);
  404. };
  405. export const TestButton = (title: string, onClick: () => void) => {
  406. return (
  407. <React.Fragment>
  408. <div>
  409. <button className='rounded-md bg-blue-400 p-4' type='button' onClick={() => onClick()}>
  410. {title}
  411. </button>
  412. </div>
  413. </React.Fragment>
  414. );
  415. };