mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +00:00 
			
		
		
		
	(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
This commit is contained in:
		
							parent
							
								
									d0318af39b
								
							
						
					
					
						commit
						7e05284cf2
					
				@ -228,7 +228,7 @@ BaseView.commonCommands = {
 | 
				
			|||||||
    this.scrollToCursor(true).catch(reportError);
 | 
					    this.scrollToCursor(true).catch(reportError);
 | 
				
			||||||
    this.activateEditorAtCursor({init});
 | 
					    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()); },
 | 
					  insertRecordBefore: function() { this.insertRow(this.cursor.rowIndex()); },
 | 
				
			||||||
  insertRecordAfter: function() { this.insertRow(this.cursor.rowIndex() + 1); },
 | 
					  insertRecordAfter: function() { this.insertRow(this.cursor.rowIndex() + 1); },
 | 
				
			||||||
@ -243,6 +243,12 @@ BaseView.commonCommands = {
 | 
				
			|||||||
  filterByThisCellValue: function() { this.filterByThisCellValue(); },
 | 
					  filterByThisCellValue: function() { this.filterByThisCellValue(); },
 | 
				
			||||||
  duplicateRows: function() { this._duplicateRows().catch(reportError); },
 | 
					  duplicateRows: function() { this._duplicateRows().catch(reportError); },
 | 
				
			||||||
  openDiscussion: function() { this.openDiscussionAtCursor(); },
 | 
					  openDiscussion: function() { this.openDiscussionAtCursor(); },
 | 
				
			||||||
 | 
					  viewAsCard: function() {
 | 
				
			||||||
 | 
					    /* Overridden by subclasses.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * This is still needed so that <space> doesn't trigger the `input` command
 | 
				
			||||||
 | 
					     * if a subclass doesn't support opening the current record as a card. */
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
BaseView.prototype.selectedRows = function() {
 | 
					BaseView.prototype.selectedRows = function() {
 | 
				
			||||||
 | 
				
			|||||||
@ -308,7 +308,7 @@ GridView.gridCommands = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  fieldEditSave: function() { this.cursor.rowIndex(this.cursor.rowIndex() + 1); },
 | 
					  fieldEditSave: function() { this.cursor.rowIndex(this.cursor.rowIndex() + 1); },
 | 
				
			||||||
  // Re-define editField after fieldEditSave to make it take precedence for the Enter key.
 | 
					  // 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) {
 | 
					  insertFieldBefore: function(maybeKeyboardEvent) {
 | 
				
			||||||
    if (!maybeKeyboardEvent) {
 | 
					    if (!maybeKeyboardEvent) {
 | 
				
			||||||
 | 
				
			|||||||
@ -71,7 +71,6 @@ export type CommandName =
 | 
				
			|||||||
  | 'input'
 | 
					  | 'input'
 | 
				
			||||||
  | 'editLabel'
 | 
					  | 'editLabel'
 | 
				
			||||||
  | 'editLayout'
 | 
					  | 'editLayout'
 | 
				
			||||||
  | 'toggleCheckbox'
 | 
					 | 
				
			||||||
  | 'historyPrevious'
 | 
					  | 'historyPrevious'
 | 
				
			||||||
  | 'historyNext'
 | 
					  | 'historyNext'
 | 
				
			||||||
  | 'makeFormula'
 | 
					  | 'makeFormula'
 | 
				
			||||||
@ -273,7 +272,7 @@ export const groups: CommendGroupDef[] = [{
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      name: 'viewAsCard',
 | 
					      name: 'viewAsCard',
 | 
				
			||||||
      keys: [],
 | 
					      keys: ['Space'],
 | 
				
			||||||
      desc: 'Show the record card widget of the selected record',
 | 
					      desc: 'Show the record card widget of the selected record',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  ]
 | 
					  ]
 | 
				
			||||||
@ -483,10 +482,6 @@ export const groups: CommendGroupDef[] = [{
 | 
				
			|||||||
      name: 'editLayout',
 | 
					      name: 'editLayout',
 | 
				
			||||||
      keys: [],
 | 
					      keys: [],
 | 
				
			||||||
      desc: 'Edit record layout'
 | 
					      desc: 'Edit record layout'
 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
      name: 'toggleCheckbox',
 | 
					 | 
				
			||||||
      keys: ['Enter', 'Space'],
 | 
					 | 
				
			||||||
      desc: 'Toggles the value of checkbox cells'
 | 
					 | 
				
			||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
      name: 'historyPrevious',
 | 
					      name: 'historyPrevious',
 | 
				
			||||||
      keys: ['Up'],
 | 
					      keys: ['Up'],
 | 
				
			||||||
 | 
				
			|||||||
@ -10,9 +10,10 @@ dispose.makeDisposable(CheckBoxEditor);
 | 
				
			|||||||
_.extend(CheckBoxEditor.prototype, TextEditor.prototype);
 | 
					_.extend(CheckBoxEditor.prototype, TextEditor.prototype);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// For documentation, see NewBaseEditor.ts
 | 
					// For documentation, see NewBaseEditor.ts
 | 
				
			||||||
CheckBoxEditor.skipEditor = function(typedVal, cellVal) {
 | 
					CheckBoxEditor.skipEditor = function(typedVal, cellVal, {event}) {
 | 
				
			||||||
  if (typedVal === ' ') {
 | 
					  // eslint-disable-next-line no-undef
 | 
				
			||||||
    // This is a special case when user hits <space>. We return the toggled value to save, and by
 | 
					  if (typedVal === '<enter>' || (event && event instanceof KeyboardEvent)) {
 | 
				
			||||||
 | 
					    // This is a special case when user hits <enter>. We return the toggled value to save, and by
 | 
				
			||||||
    // this indicate that the editor should not open.
 | 
					    // this indicate that the editor should not open.
 | 
				
			||||||
    return !cellVal;
 | 
					    return !cellVal;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -699,6 +699,7 @@ export class FieldBuilder extends Disposable {
 | 
				
			|||||||
  public buildEditorDom(editRow: DataRowModel, mainRowModel: DataRowModel, options: {
 | 
					  public buildEditorDom(editRow: DataRowModel, mainRowModel: DataRowModel, options: {
 | 
				
			||||||
    init?: string,
 | 
					    init?: string,
 | 
				
			||||||
    state?: any
 | 
					    state?: any
 | 
				
			||||||
 | 
					    event?: KeyboardEvent | MouseEvent
 | 
				
			||||||
  }) {
 | 
					  }) {
 | 
				
			||||||
    // If the user attempts to edit a value during transform, finalize (i.e. cancel or execute)
 | 
					    // If the user attempts to edit a value during transform, finalize (i.e. cancel or execute)
 | 
				
			||||||
    // the transform.
 | 
					    // the transform.
 | 
				
			||||||
@ -733,7 +734,13 @@ export class FieldBuilder extends Disposable {
 | 
				
			|||||||
      return clearOwn();
 | 
					      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();
 | 
					      return clearOwn();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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,
 | 
					 * 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(
 | 
					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 {
 | 
					): boolean {
 | 
				
			||||||
 | 
					  const {typedVal, event} = options;
 | 
				
			||||||
  // Never skip the editor if editing a formula. Also, check that skipEditor static function
 | 
					  // 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).
 | 
					  // 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) {
 | 
					  if (!field.column.peek().isRealFormula.peek() && editorCtor.skipEditor) {
 | 
				
			||||||
    const origVal = editRow.cells[field.colId()].peek();
 | 
					    const origVal = editRow.cells[field.colId()].peek();
 | 
				
			||||||
    const skipEditorValue = editorCtor.skipEditor(typedVal, origVal);
 | 
					    const skipEditorValue = editorCtor.skipEditor(typedVal, origVal, {event});
 | 
				
			||||||
    if (skipEditorValue !== undefined) {
 | 
					    if (skipEditorValue !== undefined) {
 | 
				
			||||||
      setAndSave(editRow, field, skipEditorValue).catch(reportError);
 | 
					      setAndSave(editRow, field, skipEditorValue).catch(reportError);
 | 
				
			||||||
      return true;
 | 
					      return true;
 | 
				
			||||||
 | 
				
			|||||||
@ -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,
 | 
					   * 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 <enter>, 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;
 | 
					    return undefined;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -86,6 +86,7 @@ export class ReferenceList extends Reference {
 | 
				
			|||||||
                  ev.stopPropagation();
 | 
					                  ev.stopPropagation();
 | 
				
			||||||
                  ev.preventDefault();
 | 
					                  ev.preventDefault();
 | 
				
			||||||
                }),
 | 
					                }),
 | 
				
			||||||
 | 
					                testId('ref-list-link-icon'),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
              cssLabel(isBlankReference ? '[Blank]' : formattedValue,
 | 
					              cssLabel(isBlankReference ? '[Blank]' : formattedValue,
 | 
				
			||||||
                testId('ref-list-cell-token-label'),
 | 
					                testId('ref-list-cell-token-label'),
 | 
				
			||||||
 | 
				
			|||||||
@ -18,11 +18,11 @@ abstract class ToggleBase extends NewAbstractWidget {
 | 
				
			|||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (!this.field.column().isRealFormula()) {
 | 
					        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
 | 
					          // flow which is handled by CheckBoxEditor.skipEditor(). This way the edit applies to
 | 
				
			||||||
          // editRow, which handles setting default values based on widget linking.
 | 
					          // editRow, which handles setting default values based on widget linking.
 | 
				
			||||||
          commands.allCommands.setCursor.run(row, this.field);
 | 
					          commands.allCommands.setCursor.run(row, this.field);
 | 
				
			||||||
          commands.allCommands.input.run(' ');
 | 
					          commands.allCommands.input.run('<enter>');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
      dom.on('dblclick', (event) => {
 | 
					      dom.on('dblclick', (event) => {
 | 
				
			||||||
 | 
				
			|||||||
@ -448,7 +448,7 @@ describe('ChoiceList', function() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  it('should allow reasonable conversions between ChoiceList and other types', async function() {
 | 
					  it('should allow reasonable conversions between ChoiceList and other types', async function() {
 | 
				
			||||||
    await gu.enterGridRows({rowNum: 1, col: 'A'},
 | 
					    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();
 | 
					    await testTextChoiceListConversions();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -534,7 +534,7 @@ describe('ChoiceList', function() {
 | 
				
			|||||||
    assert.deepEqual(await gu.getVisibleGridCells({rowNums: [1, 2, 3], cols: ['A']}), [
 | 
					    assert.deepEqual(await gu.getVisibleGridCells({rowNums: [1, 2, 3], cols: ['A']}), [
 | 
				
			||||||
      'Hello',
 | 
					      'Hello',
 | 
				
			||||||
      'World',
 | 
					      '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.
 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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 () {
 | 
					  it('shows preview of summary table when clicked', async function () {
 | 
				
			||||||
    // Open a summary table.
 | 
					    // Open a summary table.
 | 
				
			||||||
    await driver.findContent('.test-raw-data-table-title', 'CountryLanguage [by Country]').click();
 | 
					    await driver.findContent('.test-raw-data-table-title', 'CountryLanguage [by Country]').click();
 | 
				
			||||||
@ -612,6 +633,139 @@ describe('RawData', function () {
 | 
				
			|||||||
    await gu.checkForErrors();
 | 
					    await gu.checkForErrors();
 | 
				
			||||||
    await assertNoPopup();
 | 
					    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;
 | 
					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());
 | 
					  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) {
 | 
					async function openMenu(tableId: string) {
 | 
				
			||||||
  const allTables = await getRawTableIds();
 | 
					  const allTables = await getRawTableIds();
 | 
				
			||||||
  const tableIndex = allTables.indexOf(tableId);
 | 
					  const tableIndex = allTables.indexOf(tableId);
 | 
				
			||||||
@ -716,6 +882,37 @@ async function isRemovable(tableId: string){
 | 
				
			|||||||
  return disabledItems.length === 0;
 | 
					  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() {
 | 
					async function waitForPopup() {
 | 
				
			||||||
  assert.isTrue(await driver.findWait('.test-raw-data-overlay', 100).isDisplayed());
 | 
					  assert.isTrue(await driver.findWait('.test-raw-data-overlay', 100).isDisplayed());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										140
									
								
								test/nbrowser/RecordCards.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								test/nbrowser/RecordCards.ts
									
									
									
									
									
										Normal file
									
								
							@ -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);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user