From 7e05284cf29ac8b18cebc2612415fec124308451 Mon Sep 17 00:00:00 2001 From: George Gevoian Date: Fri, 8 Dec 2023 00:59:42 -0500 Subject: [PATCH] (core) Add shortcut for opening Record Card Summary: Also adds tests for previously untested Record Card behavior. Test Plan: Browser tests. Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D4136 --- app/client/components/BaseView.js | 8 +- app/client/components/GridView.js | 2 +- app/client/components/commandList.ts | 7 +- app/client/widgets/CheckBoxEditor.js | 7 +- app/client/widgets/FieldBuilder.ts | 9 +- app/client/widgets/FieldEditor.ts | 10 +- app/client/widgets/NewBaseEditor.ts | 8 +- app/client/widgets/ReferenceList.ts | 1 + app/client/widgets/Toggle.ts | 4 +- test/nbrowser/ChoiceList.ts | 4 +- test/nbrowser/RawData.ts | 197 +++++++++++++++++++++++++++ test/nbrowser/RecordCards.ts | 140 +++++++++++++++++++ 12 files changed, 376 insertions(+), 21 deletions(-) create mode 100644 test/nbrowser/RecordCards.ts diff --git a/app/client/components/BaseView.js b/app/client/components/BaseView.js index 8134fcce..68af3bc8 100644 --- a/app/client/components/BaseView.js +++ b/app/client/components/BaseView.js @@ -228,7 +228,7 @@ BaseView.commonCommands = { this.scrollToCursor(true).catch(reportError); this.activateEditorAtCursor({init}); }, - editField: function() { closeRegisteredMenu(); this.scrollToCursor(true); this.activateEditorAtCursor(); }, + editField: function(event) { closeRegisteredMenu(); this.scrollToCursor(true); this.activateEditorAtCursor({event}); }, insertRecordBefore: function() { this.insertRow(this.cursor.rowIndex()); }, insertRecordAfter: function() { this.insertRow(this.cursor.rowIndex() + 1); }, @@ -243,6 +243,12 @@ BaseView.commonCommands = { filterByThisCellValue: function() { this.filterByThisCellValue(); }, duplicateRows: function() { this._duplicateRows().catch(reportError); }, openDiscussion: function() { this.openDiscussionAtCursor(); }, + viewAsCard: function() { + /* Overridden by subclasses. + * + * This is still needed so that doesn't trigger the `input` command + * if a subclass doesn't support opening the current record as a card. */ + }, }; BaseView.prototype.selectedRows = function() { diff --git a/app/client/components/GridView.js b/app/client/components/GridView.js index f188ef19..633cbb8a 100644 --- a/app/client/components/GridView.js +++ b/app/client/components/GridView.js @@ -308,7 +308,7 @@ GridView.gridCommands = { fieldEditSave: function() { this.cursor.rowIndex(this.cursor.rowIndex() + 1); }, // Re-define editField after fieldEditSave to make it take precedence for the Enter key. - editField: function() { closeRegisteredMenu(); this.scrollToCursor(true); this.activateEditorAtCursor(); }, + editField: function(event) { closeRegisteredMenu(); this.scrollToCursor(true); this.activateEditorAtCursor({event}); }, insertFieldBefore: function(maybeKeyboardEvent) { if (!maybeKeyboardEvent) { diff --git a/app/client/components/commandList.ts b/app/client/components/commandList.ts index 08f2bb0f..9b402171 100644 --- a/app/client/components/commandList.ts +++ b/app/client/components/commandList.ts @@ -71,7 +71,6 @@ export type CommandName = | 'input' | 'editLabel' | 'editLayout' - | 'toggleCheckbox' | 'historyPrevious' | 'historyNext' | 'makeFormula' @@ -273,7 +272,7 @@ export const groups: CommendGroupDef[] = [{ }, { name: 'viewAsCard', - keys: [], + keys: ['Space'], desc: 'Show the record card widget of the selected record', }, ] @@ -483,10 +482,6 @@ export const groups: CommendGroupDef[] = [{ name: 'editLayout', keys: [], desc: 'Edit record layout' - }, { - name: 'toggleCheckbox', - keys: ['Enter', 'Space'], - desc: 'Toggles the value of checkbox cells' }, { name: 'historyPrevious', keys: ['Up'], diff --git a/app/client/widgets/CheckBoxEditor.js b/app/client/widgets/CheckBoxEditor.js index 9c7d6662..226f9710 100644 --- a/app/client/widgets/CheckBoxEditor.js +++ b/app/client/widgets/CheckBoxEditor.js @@ -10,9 +10,10 @@ dispose.makeDisposable(CheckBoxEditor); _.extend(CheckBoxEditor.prototype, TextEditor.prototype); // For documentation, see NewBaseEditor.ts -CheckBoxEditor.skipEditor = function(typedVal, cellVal) { - if (typedVal === ' ') { - // This is a special case when user hits . We return the toggled value to save, and by +CheckBoxEditor.skipEditor = function(typedVal, cellVal, {event}) { + // eslint-disable-next-line no-undef + if (typedVal === '' || (event && event instanceof KeyboardEvent)) { + // This is a special case when user hits . We return the toggled value to save, and by // this indicate that the editor should not open. return !cellVal; } diff --git a/app/client/widgets/FieldBuilder.ts b/app/client/widgets/FieldBuilder.ts index eab4b46b..9139b343 100644 --- a/app/client/widgets/FieldBuilder.ts +++ b/app/client/widgets/FieldBuilder.ts @@ -699,6 +699,7 @@ export class FieldBuilder extends Disposable { public buildEditorDom(editRow: DataRowModel, mainRowModel: DataRowModel, options: { init?: string, state?: any + event?: KeyboardEvent | MouseEvent }) { // If the user attempts to edit a value during transform, finalize (i.e. cancel or execute) // the transform. @@ -733,7 +734,13 @@ export class FieldBuilder extends Disposable { return clearOwn(); } - if (!this._readonly.get() && saveWithoutEditor(editorCtor, editRow, this.field, options.init)) { + if ( + !this._readonly.get() && + saveWithoutEditor(editorCtor, editRow, this.field, { + typedVal: options.init, + event: options.event, + }) + ) { return clearOwn(); } diff --git a/app/client/widgets/FieldEditor.ts b/app/client/widgets/FieldEditor.ts index 3156db9c..e473c154 100644 --- a/app/client/widgets/FieldEditor.ts +++ b/app/client/widgets/FieldEditor.ts @@ -24,16 +24,20 @@ const t = makeT('FieldEditor'); /** * Check if the typed-in value should change the cell without opening the cell editor, and if so, - * saves and returns true. E.g. on typing space, CheckBoxEditor toggles the cell without opening. + * saves and returns true. E.g. on typing enter, CheckBoxEditor toggles the cell without opening. */ export function saveWithoutEditor( - editorCtor: IEditorConstructor, editRow: DataRowModel, field: ViewFieldRec, typedVal: string|undefined + editorCtor: IEditorConstructor, + editRow: DataRowModel, + field: ViewFieldRec, + options: {typedVal?: string, event?: KeyboardEvent|MouseEvent} ): boolean { + const {typedVal, event} = options; // Never skip the editor if editing a formula. Also, check that skipEditor static function // exists (we don't bother adding it on old-style JS editors that don't need it). if (!field.column.peek().isRealFormula.peek() && editorCtor.skipEditor) { const origVal = editRow.cells[field.colId()].peek(); - const skipEditorValue = editorCtor.skipEditor(typedVal, origVal); + const skipEditorValue = editorCtor.skipEditor(typedVal, origVal, {event}); if (skipEditorValue !== undefined) { setAndSave(editRow, field, skipEditorValue).catch(reportError); return true; diff --git a/app/client/widgets/NewBaseEditor.ts b/app/client/widgets/NewBaseEditor.ts index 178d61dd..69846a70 100644 --- a/app/client/widgets/NewBaseEditor.ts +++ b/app/client/widgets/NewBaseEditor.ts @@ -54,9 +54,13 @@ export abstract class NewBaseEditor extends Disposable { /** * Check if the typed-in value should change the cell without opening the editor, and if so, - * returns the value to save. E.g. on typing " ", CheckBoxEditor toggles value without opening. + * returns the value to save. E.g. on typing , CheckBoxEditor toggles value without opening. */ - public static skipEditor(typedVal: string|undefined, origVal: CellValue): CellValue|undefined { + public static skipEditor( + typedVal: string|undefined, + origVal: CellValue, + options?: {event?: KeyboardEvent|MouseEvent} + ): CellValue|undefined { return undefined; } diff --git a/app/client/widgets/ReferenceList.ts b/app/client/widgets/ReferenceList.ts index e4a74b81..bddff202 100644 --- a/app/client/widgets/ReferenceList.ts +++ b/app/client/widgets/ReferenceList.ts @@ -86,6 +86,7 @@ export class ReferenceList extends Reference { ev.stopPropagation(); ev.preventDefault(); }), + testId('ref-list-link-icon'), ), cssLabel(isBlankReference ? '[Blank]' : formattedValue, testId('ref-list-cell-token-label'), diff --git a/app/client/widgets/Toggle.ts b/app/client/widgets/Toggle.ts index 8a8d2222..164f8324 100644 --- a/app/client/widgets/Toggle.ts +++ b/app/client/widgets/Toggle.ts @@ -18,11 +18,11 @@ abstract class ToggleBase extends NewAbstractWidget { return; } if (!this.field.column().isRealFormula()) { - // Move the cursor here, and pretend that spacebar was pressed. This triggers an editing + // Move the cursor here, and pretend that enter was pressed. This triggers an editing // flow which is handled by CheckBoxEditor.skipEditor(). This way the edit applies to // editRow, which handles setting default values based on widget linking. commands.allCommands.setCursor.run(row, this.field); - commands.allCommands.input.run(' '); + commands.allCommands.input.run(''); } }), dom.on('dblclick', (event) => { diff --git a/test/nbrowser/ChoiceList.ts b/test/nbrowser/ChoiceList.ts index f894648b..442e0c10 100644 --- a/test/nbrowser/ChoiceList.ts +++ b/test/nbrowser/ChoiceList.ts @@ -448,7 +448,7 @@ describe('ChoiceList', function() { it('should allow reasonable conversions between ChoiceList and other types', async function() { await gu.enterGridRows({rowNum: 1, col: 'A'}, - [['Hello'], ['World'], [' Foo,Bar;Baz!,"Qux, quux corge", "80\'s",']]); + [['Hello'], ['World'], ['Foo,Bar;Baz!,"Qux, quux corge", "80\'s",']]); await testTextChoiceListConversions(); }); @@ -534,7 +534,7 @@ describe('ChoiceList', function() { assert.deepEqual(await gu.getVisibleGridCells({rowNums: [1, 2, 3], cols: ['A']}), [ 'Hello', 'World', - ' Foo,Bar;Baz!,"Qux, quux corge", "80\'s",', // That's the text originally entered into this Text cell. + 'Foo,Bar;Baz!,"Qux, quux corge", "80\'s",', // That's the text originally entered into this Text cell. ]); } diff --git a/test/nbrowser/RawData.ts b/test/nbrowser/RawData.ts index 8b200975..73ec453a 100644 --- a/test/nbrowser/RawData.ts +++ b/test/nbrowser/RawData.ts @@ -269,6 +269,27 @@ describe('RawData', function () { ]); }); + it('shows Record Card button for all non-summary tables', async function () { + const displayed = await getRawTableRecordCardButtonsIsDisplayed(); + assert.deepEqual(displayed, [ + true, + true, + true, + true, + false, + false, + ]); + const enabled = await getRawTableRecordCardButtonsIsEnabled(); + assert.deepEqual(enabled, [ + true, + true, + true, + true, + false, + false, + ]); + }); + it('shows preview of summary table when clicked', async function () { // Open a summary table. await driver.findContent('.test-raw-data-table-title', 'CountryLanguage [by Country]').click(); @@ -612,6 +633,139 @@ describe('RawData', function () { await gu.checkForErrors(); await assertNoPopup(); }); + + it("can edit a table's Record Card", async () => { + // Open the Record Card for the Country table. + await openRawData(); + await editRecordCard('Country'); + + // Check that the Record Card is shown. + assert.isTrue(await driver.findWait('.test-raw-data-overlay', 100).isDisplayed()); + + // Check that layout editing is toggled by default. + assert.isTrue(await driver.find('.test-edit-layout-controls').isDisplayed()); + + // Check that the title is correct. Note that it's initially obscured by the layout + // editing buttons; it becomes visible after the layout is saved. + assert.equal(await gu.getSectionTitle(), 'COUNTRY Card'); + + // Modify the layout and theme. + await gu.openWidgetPanel('widget'); + assert.isTrue( + await driver.findContent('.active_section .g_record_detail_inner .g_record_detail_label', + gu.exactMatch('Continent')).isPresent() + ); + await gu.moveToHidden('Continent'); + assert.isFalse( + await driver.findContent('.active_section .g_record_detail_inner .g_record_detail_label', + gu.exactMatch('Continent')).isPresent() + ); + await driver.findContent('.test-edit-layout-controls button', 'Save').click(); + await gu.waitForServer(); + await driver.find('.test-vconfigtab-detail-theme').click(); + await driver.findContent('.test-select-row', /Blocks/).click(); + await gu.waitForServer(); + await gu.checkForErrors(); + + // Close the overlay. + await gu.sendKeys(Key.ESCAPE); + + // Re-open the Record Card and check that the new layout and theme persisted. + await editRecordCard('Country'); + assert.isFalse( + await driver.findContent('.active_section .g_record_detail_inner .g_record_detail_label', + gu.exactMatch('Continent')).isPresent() + ); + assert.equal( + await driver.find('.test-vconfigtab-detail-theme').getText(), + 'Blocks' + ); + await gu.sendKeys(Key.ESCAPE, Key.ESCAPE); + + // Open the Record Card from outside the Raw Data page and check that the + // new layout and theme is used. + await gu.openPage('Country'); + await (await gu.openRowMenu(1)).findContent('li', /View as card/).click(); + assert.isTrue(await driver.findWait('.test-record-card-popup-overlay', 100).isDisplayed()); + assert.isFalse( + await driver.findContent('.active_section .g_record_detail_inner .g_record_detail_label', + gu.exactMatch('Continent')).isPresent() + ); + assert.equal( + await driver.find('.test-vconfigtab-detail-theme').getText(), + 'Blocks' + ); + await gu.sendKeys(Key.ESCAPE); + }); + + it("can disable a table's Record Card", async () => { + // Disable the Record Card for the Country table. + await openRawData(); + await disableRecordCard('Country'); + + // Check that the button to edit the Record Card is disabled. + assert.isFalse(await isRecordCardEnabled('Country')); + await editRecordCard('Country'); + assert.isFalse(await driver.find('.test-raw-data-overlay').isPresent()); + + // Check that the Edit Record Card menu item still works though. + await openMenu('Country'); + await driver.find('.test-raw-data-menu-edit-record-card').click(); + assert.isTrue(await driver.findWait('.test-raw-data-overlay', 100).isDisplayed()); + assert.equal(await gu.getSectionTitle(), 'COUNTRY Card'); + + // Stop editing the layout and close the overlay. + await gu.sendKeys(Key.ESCAPE, Key.ESCAPE); + + // Check that it's no longer possible to open a Record Card from outside + // the Raw Data page, even with the keyboard shortcut. + await gu.openPage('Country'); + await (await gu.openRowMenu(1)).findContent('li.disabled', /View as card/); + await gu.sendKeys(Key.ESCAPE, Key.SPACE); + assert.isFalse(await driver.find('.test-record-card-popup-overlay').isPresent()); + + // Check that clicking the icon in Reference and Reference List columns also + // doesn't open a Record Card. + await gu.openPage('CountryLanguage'); + await gu.getCell(0, 1).find('.test-ref-link-icon').click(); + assert.isFalse(await driver.find('.test-record-card-popup-overlay').isPresent()); + await gu.setType('Reference List', {apply: true}); + await gu.getCell(0, 1).find('.test-ref-list-link-icon').click(); + assert.isFalse(await driver.find('.test-record-card-popup-overlay').isPresent()); + }); + + it("can enable a table's Record Card", async () => { + // Enable the Record Card for the Country table. + await openRawData(); + await enableRecordCard('Country'); + + // Check that the button to edit the Record Card is enabled again. + assert.isTrue(await isRecordCardEnabled('Country')); + await editRecordCard('Country'); + assert.isTrue(await driver.findWait('.test-raw-data-overlay', 100).isDisplayed()); + assert.equal(await gu.getSectionTitle(), 'COUNTRY Card'); + + // Check that it's possible again to open the Record Card from outside + // the Raw Data page. + await gu.openPage('Country'); + await (await gu.openRowMenu(1)).findContent('li', /View as card/).click(); + assert.isTrue(await driver.findWait('.test-record-card-popup-overlay', 100).isDisplayed()); + await gu.sendKeys(Key.ESCAPE); + assert.isFalse(await driver.find('.test-record-card-popup-overlay').isPresent()); + await gu.sendKeys(Key.SPACE); + assert.isTrue(await driver.findWait('.test-record-card-popup-overlay', 100).isDisplayed()); + + // Check that clicking the icon in Reference and Reference List columns opens a + // Record Card again. + await gu.openPage('CountryLanguage'); + await gu.getCell(0, 1).find('.test-ref-list-link-icon').click(); + assert.isTrue(await driver.findWait('.test-record-card-popup-overlay', 100).isDisplayed()); + await gu.sendKeys(Key.ESCAPE); + assert.isFalse(await driver.find('.test-record-card-popup-overlay').isPresent()); + await gu.setType('Reference', {apply: true}); + await gu.getCell(0, 1).find('.test-ref-link-icon').click(); + assert.isTrue(await driver.findWait('.test-record-card-popup-overlay', 100).isDisplayed()); + }); }); const anchorRegex = /#a(\d+)\.s(\d+)\.r(\d+)\.c(\d+)/gm; @@ -695,6 +849,18 @@ async function getRawTableRows() { return await driver.findAll('.test-raw-data-table-rows', e => e.getText()); } +async function getRawTableRecordCardButtonsIsDisplayed() { + return await driver.findAll('.test-raw-data-table-record-card', e => e.isDisplayed()); +} + +async function getRawTableRecordCardButtonsIsEnabled() { + return await driver.findAll('.test-raw-data-table-record-card', async e => { + const isDisplayed = await e.isDisplayed(); + const className = await e.getAttribute('class'); + return isDisplayed && !className.includes('-disabled'); + }); +} + async function openMenu(tableId: string) { const allTables = await getRawTableIds(); const tableIndex = allTables.indexOf(tableId); @@ -716,6 +882,37 @@ async function isRemovable(tableId: string){ return disabledItems.length === 0; } +async function editRecordCard(tableId: string, wait = true) { + await driver.findContent('.test-raw-data-table-title', tableId) + .findClosest('.test-raw-data-table') + .find('.test-raw-data-table-record-card') + .click(); + if (wait) { + await gu.waitForServer(); + } +} + +async function disableRecordCard(tableId: string) { + await openMenu(tableId); + await driver.find('.test-raw-data-menu-disable-record-card').click(); + await gu.waitForServer(); +} + +async function enableRecordCard(tableId: string) { + await openMenu(tableId); + await driver.find('.test-raw-data-menu-enable-record-card').click(); + await gu.waitForServer(); +} + +async function isRecordCardEnabled(tableId: string) { + const recordCard = await driver.findContent('.test-raw-data-table-title', tableId) + .findClosest('.test-raw-data-table') + .find('.test-raw-data-table-record-card'); + const isDisplayed = await recordCard.isDisplayed(); + const className = await recordCard.getAttribute('class'); + return isDisplayed && !className.includes('-disabled'); +} + async function waitForPopup() { assert.isTrue(await driver.findWait('.test-raw-data-overlay', 100).isDisplayed()); } diff --git a/test/nbrowser/RecordCards.ts b/test/nbrowser/RecordCards.ts new file mode 100644 index 00000000..102ac82b --- /dev/null +++ b/test/nbrowser/RecordCards.ts @@ -0,0 +1,140 @@ +import {UserAPI} from 'app/common/UserAPI'; +import {assert, driver, Key} from 'mocha-webdriver'; +import * as gu from 'test/nbrowser/gristUtils'; +import {setupTestSuite} from 'test/nbrowser/testUtils'; + +describe('RecordCards', function() { + this.timeout(30000); + let api: UserAPI; + let docId: string; + let session: gu.Session; + const cleanup = setupTestSuite(); + + before(async function() { + session = await gu.session().login(); + docId = (await session.tempDoc(cleanup, 'World-v39.grist')).id; + api = session.createHomeApi(); + await gu.openPage('Country'); + }); + + afterEach(() => gu.checkForErrors()); + + describe('RowContextMenu', function() { + it('opens popup when keyboard shortcut is pressed', async function() { + await gu.sendKeys(Key.SPACE); + assert.isTrue(await driver.findWait('.test-record-card-popup-overlay', 100).isDisplayed()); + assert.equal(await driver.find('.test-widget-title-text').getText(), 'COUNTRY Card'); + assert.equal(await gu.getCardCell('Code').getText(), 'ALB'); + assert.isFalse(await driver.find('.grist-single-record__menu').isPresent()); + await gu.sendKeys(Key.ESCAPE); + }); + + it('opens popup when menu item is clicked', async function() { + await (await gu.openRowMenu(2)).findContent('li', /View as card/).click(); + assert.isTrue(await driver.findWait('.test-record-card-popup-overlay', 100).isDisplayed()); + assert.equal(await driver.find('.test-widget-title-text').getText(), 'COUNTRY Card'); + assert.equal(await gu.getCardCell('Code').getText(), 'AND'); + await gu.sendKeys(Key.ESCAPE); + }); + + it('closes popup when record is deleted', async function() { + await api.applyUserActions(docId, [ + ['RemoveRecord', 'Country', 1] + ]); + await gu.waitToPass(async () => { + assert.isFalse(await driver.find('.test-record-card-popup-overlay').isPresent()); + }, 2000); + + await (await gu.openRowMenu(1)).findContent('li', /View as card/).click(); + assert.isTrue(await driver.findWait('.test-record-card-popup-overlay', 100).isDisplayed()); + await gu.sendKeys(Key.chord(await gu.modKey(), Key.DELETE)); + await driver.find('.test-confirm-save').click(); + await gu.waitForServer(); + assert.isFalse(await driver.find('.test-record-card-popup-overlay').isPresent()); + }); + + it('hides option to open popup if more than 1 row is selected', async function() { + await gu.sendKeys(Key.chord(Key.SHIFT, Key.DOWN)); + assert.isFalse(await (await gu.openRowMenu(1)).findContent('li', /View as card/).isPresent()); + await gu.sendKeys(Key.ESCAPE, Key.SPACE); + assert.isFalse(await driver.find('.test-record-card-popup-overlay').isPresent()); + }); + + it('disables option to open popup in "add new" row', async function() { + await gu.sendKeys(Key.chord(await gu.modKey(), Key.DOWN)); + assert.isTrue(await (await gu.openRowMenu(120)).findContent('li.disabled', /View as card/).isPresent()); + await gu.sendKeys(Key.ESCAPE, Key.SPACE); + assert.isFalse(await driver.find('.test-record-card-popup-overlay').isPresent()); + }); + }); + + describe('Reference', function() { + before(async function() { + await gu.openPage('CountryLanguage'); + }); + + it('opens popup when reference icon is clicked', async function() { + await gu.getCell(0, 4).find('.test-ref-link-icon').click(); + assert.isTrue(await driver.findWait('.test-record-card-popup-overlay', 100).isDisplayed()); + assert.equal(await driver.find('.test-widget-title-text').getText(), 'COUNTRY Card'); + assert.equal(await gu.getCardCell('Code').getText(), 'AFG'); + assert.isFalse(await driver.find('.grist-single-record__menu').isPresent()); + await gu.sendKeys(Key.ESCAPE); + }); + + it('updates popup when reference icon is clicked within Record Card popup', async function() { + await gu.getCell(0, 4).find('.test-ref-text').click(); + await gu.sendKeys(Key.SPACE); + assert.isTrue(await driver.findWait('.test-record-card-popup-overlay', 100).isDisplayed()); + assert.equal(await driver.find('.test-widget-title-text').getText(), 'COUNTRYLANGUAGE Card'); + assert.equal(await gu.getCardCell('Country').getText(), 'AFG'); + await gu.getCardCell('Country').find('.test-ref-link-icon').click(); + assert.equal(await driver.find('.test-widget-title-text').getText(), 'COUNTRY Card'); + assert.equal(await gu.getCardCell('Code').getText(), 'AFG'); + await gu.sendKeys(Key.ESCAPE); + }); + + it('does not open popup if cell is empty', async function() { + await gu.getCell(0, 4).find('.test-ref-text').click(); + await driver.sendKeys(Key.DELETE); + await gu.waitForServer(); + await gu.getCell(0, 4).find('.test-ref-link-icon').click(); + assert.isFalse(await driver.find('.test-record-card-popup-overlay').isPresent()); + await gu.undo(); + }); + + it('does not open popup in "add new" row', async function() { + await gu.sendKeys(Key.chord(await gu.modKey(), Key.DOWN)); + await gu.getCell(0, 747).find('.test-ref-link-icon').click(); + assert.isFalse(await driver.find('.test-record-card-popup-overlay').isPresent()); + }); + }); + + describe('ReferenceList', function() { + before(async function() { + await gu.sendKeys(Key.chord(await gu.modKey(), Key.UP)); + await gu.setType('Reference List', {apply: true}); + }); + + it('opens popup when reference icon is clicked', async function() { + await gu.getCell(0, 4).find('.test-ref-list-link-icon').click(); + assert.isTrue(await driver.findWait('.test-record-card-popup-overlay', 100).isDisplayed()); + assert.equal(await driver.find('.test-widget-title-text').getText(), 'COUNTRY Card'); + assert.equal(await gu.getCardCell('Code').getText(), 'AFG'); + assert.isFalse(await driver.find('.grist-single-record__menu').isPresent()); + await gu.sendKeys(Key.ESCAPE); + }); + + it('updates popup when reference icon is clicked within Record Card popup', async function() { + await gu.getCell(0, 4).click(); + await gu.sendKeys(Key.SPACE); + assert.isTrue(await driver.findWait('.test-record-card-popup-overlay', 100).isDisplayed()); + assert.equal(await driver.find('.test-widget-title-text').getText(), 'COUNTRYLANGUAGE Card'); + assert.equal(await gu.getCardCell('Country').getText(), 'AFG'); + await gu.getCardCell('Country').find('.test-ref-list-link-icon').click(); + assert.equal(await driver.find('.test-widget-title-text').getText(), 'COUNTRY Card'); + assert.equal(await gu.getCardCell('Code').getText(), 'AFG'); + await gu.sendKeys(Key.ESCAPE); + }); + }); +});