Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

swiping through images in viewer widget #117

Merged
merged 29 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3e90529
swiping through images
JakubSerafin Dec 13, 2023
d94c4bf
first passing test
JakubSerafin Dec 15, 2023
768f034
Merge remote-tracking branch 'origin/master' into feature/image-viewe…
JakubSerafin Jan 23, 2024
670eeba
tree first test working
JakubSerafin Jan 24, 2024
3149f7c
Refactor widget setup and add custom widget mapping
JakubSerafin Jan 24, 2024
e0f5047
tests for empty image
JakubSerafin Jan 24, 2024
d3e4dee
Add image navigation functionality tests with multimage scenario
JakubSerafin Jan 24, 2024
a90ac07
swype tests
JakubSerafin Jan 25, 2024
de1f7c7
Remove redundant CSS styles commited by mistake, a bit of ode cleanup…
JakubSerafin Jan 26, 2024
70d6f53
Update package.json dependencies
JakubSerafin Jan 26, 2024
07e8530
post code review fixes
JakubSerafin Feb 5, 2024
e7783f2
Fix missing newline at end of file in script.js
JakubSerafin Feb 5, 2024
ec38efc
Add styling to button and body
JakubSerafin Feb 5, 2024
93465ba
styuling improvments and hidding buttons when touchscreen (mobile) is…
JakubSerafin Feb 5, 2024
41915f4
Update drop-shadow filter in index.html
JakubSerafin Feb 5, 2024
9907c21
refactor undo method in gristWebDriverUtils.ts
JakubSerafin Feb 5, 2024
45237dc
Add lodash.escaperegexp dependencies
JakubSerafin Feb 5, 2024
0e2b751
Add/update devDependencies in package.json
JakubSerafin Feb 5, 2024
93ea31c
set repository to registry.yarnpkg.com
JakubSerafin Feb 5, 2024
2845dba
linting, formating
JakubSerafin Feb 8, 2024
d7494f4
fixing problem with mocha firefox headless
paulfitz Nov 28, 2023
47aeea5
Update test/init-mocha-webdriver.ts
JakubSerafin Feb 14, 2024
fea7c44
test for switching diffrent state of imageviewer
JakubSerafin Feb 14, 2024
20432bf
refactor
JakubSerafin Feb 14, 2024
a39429c
more refactor
JakubSerafin Feb 14, 2024
06eed2c
even more refactor
JakubSerafin Feb 14, 2024
e8a08bc
navigation button will disapear when navigating from mult image to none
JakubSerafin Feb 14, 2024
2cbc719
Merge remote-tracking branch 'origin/feature/image-viewer-multi-image…
JakubSerafin Feb 14, 2024
9d8a672
cursor change to pointer on image navigation buttons, also test focus…
JakubSerafin Feb 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@
},
"devDependencies": {
"@types/chai": "^4.3.5",
"@types/lodash.escaperegexp": "^4.1.9",
"@types/mocha": "^10.0.1",
"@types/node": "18.11.9",
"@types/node-fetch": "^2.6.4",
"@types/selenium-webdriver": "^4.1.15",
"commander": "^11.1.0",
"live-server": "^1.2.1",
"lodash": "^4.17.21",
"lodash.escaperegexp": "^4.1.2",
"mocha": "^10.2.0",
"mocha-webdriver": "0.3.1",
"node-fetch": "^2",
Expand Down
Binary file added test/fixtures/docs/Images.grist
Binary file not shown.
Binary file added test/fixtures/images/image1.jpg
JakubSerafin marked this conversation as resolved.
Show resolved Hide resolved
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/fixtures/images/image2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/fixtures/images/image3.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 20 additions & 16 deletions test/getGrist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import fetch from 'node-fetch';
import {GristWebDriverUtils} from 'test/gristWebDriverUtils';


type UserAction = Array<string | number | object | boolean | null | undefined>;


/**
* Set up mocha hooks for starting and stopping Grist. Return
Expand Down Expand Up @@ -198,26 +198,12 @@ export class GristUtils extends GristWebDriverUtils {
await this.waitForServer();
}

public async sendActionsAndWaitForServer(actions: UserAction[], optTimeout: number = 2000) {
const result = await driver.executeAsyncScript(async (actions: any, done: Function) => {
try {
await (window as any).gristDocPageModel.gristDoc.get().docModel.docData.sendActions(actions);
done(null);
} catch (err) {
done(String(err?.message || err));
}
}, actions);
if (result) {
throw new Error(result as string);
}
await this.waitForServer(optTimeout);
}


public async clickWidgetPane() {
const elem = this.driver.find('.test-config-widget-select .test-select-open');
if (await elem.isPresent()) {
await elem.click();
// if not present, may just be already selected.
}
}

Expand All @@ -226,6 +212,20 @@ export class GristUtils extends GristWebDriverUtils {
await this.waitForServer();
}

public async removeWidget(name: string|RegExp) {
await this.focusOnWidget(name);
await this.sendCommand('deleteSection');
await this.waitForServer();
}

public async addCustomSection(name: string, type: string, dataSource: string|RegExp= /Table1/) {
await this.toggleSidePanel('right', 'open');
await this.addNewSection(/Custom/, dataSource);
await this.clickWidgetPane();
await this.selectCustomWidget(type);
await this.waitForServer();
}

public async setCustomWidgetAccess(option: "none" | "read table" | "full") {
const text = {
"none": "No document access",
Expand Down Expand Up @@ -275,6 +275,10 @@ export class GristUtils extends GristWebDriverUtils {
return this.inCustomWidget(() => this.driver.find(selector).getText());
}

public async getCustomWidgetElementParameter(selector: string, parameter: string): Promise<string> {
return this.inCustomWidget(() => this.driver.find(selector).getAttribute(parameter));
}

public async executeScriptInCustomWidget<T>(script: Function, ...args: any[]): Promise<T> {
return this.inCustomWidget(() => {
return driver.executeScript(script, ...args);
Expand Down
161 changes: 158 additions & 3 deletions test/gristWebDriverUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
* easily.
*/

import { WebDriver, WebElement } from 'mocha-webdriver';
import {Key, WebDriver, WebElement, WebElementPromise} from 'mocha-webdriver';
import escapeRegExp = require('lodash/escapeRegExp');

type SectionTypes = 'Table'|'Card'|'Card List'|'Chart'|'Custom';
type UserAction = Array<string | number | object | boolean | null | undefined>;

export class GristWebDriverUtils {
public constructor(public driver: WebDriver) {
Expand All @@ -37,6 +39,36 @@ export class GristWebDriverUtils {
));
}

public async sendActionsAndWaitForServer(actions: UserAction[], optTimeout: number = 2000) {
const result = await this.driver.executeAsyncScript(async (actions: any, done: Function) => {
try {
await (window as any).gristDocPageModel.gristDoc.get().docModel.docData.sendActions(actions);
done(null);
} catch (err) {
done(String(err?.message || err));
}
}, actions);
if (result) {
throw new Error(result as string);
}
await this.waitForServer(optTimeout);
}

/**
* Runs a Grist command in the browser window.
*/
public async sendCommand(name: string, argument: any = null) {
await this.driver.executeAsyncScript((name: any, argument: any, done: any) => {
const result = (window as any).gristApp.allCommands[name].run(argument);
if (result?.finally) {
result.finally(done);
} else {
done();
}
}, name, argument);
await this.waitForServer();
}


public async login(){
//just click log in to get example account.
Expand Down Expand Up @@ -287,7 +319,7 @@ export class GristWebDriverUtils {
*/
public async undo(optCount: number = 1, optTimeout?: number) {
for (let i = 0; i < optCount; ++i) {
await this.driver.find('.test-undo').doClick();
await this.driver.find('.test-undo').doClick();
}
await this.waitForServer(optTimeout);
}
Expand All @@ -306,6 +338,103 @@ export class GristWebDriverUtils {
await this.driver.manage().window().setRect(oldDimensions);
});
}

public async fillCell(columnName: string, row: number, value: string) {
const cell = await this.getCell({col: columnName, rowNum: row});
await cell.click();
await this.driver.sendKeys(value)
await this.driver.sendKeys(Key.ENTER);
}

public async addColumn(table:string, type: string, name: string) {
// focus on table
await this.focusOnWidget(table);
// add new column using a shortcut
await this.driver.actions().keyDown(Key.ALT).sendKeys('=').keyUp(Key.ALT).perform();
// wait for rename panel to show up
await this.driver.findWait('.test-column-title-popup', 1000);
// rename and accept
await this.driver.sendKeys(name);
await this.driver.sendKeys(Key.ENTER);
await this.waitForServer();
}

public async focusOnWidget(widgetName: string|RegExp) {
await this.driver.findContentWait('.test-widget-title-text', widgetName, 5000).click();
await this.driver.findWait(".test-widget-title-cancel", 1000).click();
await this.waitForServer();
}


/**
* Returns a visible GridView cell. Options may be given as arguments directly, or as an object.
* - col: column name, or 0-based column index
* - rowNum: 1-based row numbers, as visible in the row headers on the left of the grid.
* - section: optional name of the section to use; will use active section if omitted.
*/
getCell(col: number|string, rowNum: number, section?: string): WebElementPromise;
JakubSerafin marked this conversation as resolved.
Show resolved Hide resolved
getCell(options: ICellSelect): WebElementPromise;
getCell(colOrOptions: number|string|ICellSelect, rowNum?: number, section?: string): WebElementPromise {
const mapper = async (el: WebElement) => el;
const options: IColSelect<WebElement> = (typeof colOrOptions === 'object' ?
{col: colOrOptions.col, rowNums: [colOrOptions.rowNum], section: colOrOptions.section, mapper} :
{col: colOrOptions, rowNums: [rowNum!], section, mapper});
return new WebElementPromise(this.driver, this.getVisibleGridCells(options).then((elems) => elems[0]));
}

/**
* Returns visible cells of the GridView from a single column and one or more rows. Options may be
* given as arguments directly, or as an object.
* - col: column name, or 0-based column index
* - rowNums: array of 1-based row numbers, as visible in the row headers on the left of the grid.
* - section: optional name of the section to use; will use active section if omitted.
*
* If given by an object, then an array of columns is also supported. In this case, the return
* value is still a single array, listing all values from the first row, then the second, etc.
*
* Returns cell text by default. Mapper may be `identity` to return the cell objects.
*/
async getVisibleGridCells(col: number|string, rows: number[], section?: string): Promise<string[]>;
async getVisibleGridCells<T = string>(options: IColSelect<T>|IColsSelect<T>): Promise<T[]>;
async getVisibleGridCells<T>(
colOrOptions: number|string|IColSelect<T>|IColsSelect<T>, _rowNums?: number[], _section?: string
): Promise<T[]> {

if (typeof colOrOptions === 'object' && 'cols' in colOrOptions) {
const {rowNums, section, mapper} = colOrOptions; // tslint:disable-line:no-shadowed-variable
const columns = await Promise.all(colOrOptions.cols.map((oneCol) =>
this.getVisibleGridCells({col: oneCol, rowNums, section, mapper})));
// This zips column-wise data into a flat row-wise array of values.
return ([] as T[]).concat(...rowNums.map((r, i) => columns.map((c) => c[i])));
}

const {col, rowNums, section, mapper = el => el.getText()}: IColSelect<any> = (
typeof colOrOptions === 'object' ? colOrOptions :
{ col: colOrOptions, rowNums: _rowNums!, section: _section}
);

if (rowNums.includes(0)) {
// Row-numbers should be what the users sees: 0 is a mistake, so fail with a helpful message.
throw new Error('rowNum must not be 0');
}

const sectionElem = section ? await this.getSection(section) : await this.driver.findWait('.active_section', 4000);
const colIndex = (typeof col === 'number' ? col :
await sectionElem.findContent('.column_name', exactMatch(col)).index());

const visibleRowNums: number[] = await sectionElem.findAll('.gridview_data_row_num',
async (el) => parseInt(await el.getText(), 10));

const selector = `.gridview_data_scroll .record:not(.column_names) .field:nth-child(${colIndex + 1})`;
const fields = mapper ? await sectionElem.findAll(selector, mapper) : await sectionElem.findAll(selector);
return rowNums.map((n) => fields[visibleRowNums.indexOf(n)]);
}

getSection(sectionOrTitle: string|WebElement): WebElement|WebElementPromise {
if (typeof sectionOrTitle !== 'string') { return sectionOrTitle; }
return this.driver.findContent(`.test-viewsection-title`, new RegExp("^" + escapeRegExp(sectionOrTitle) + "$", 'i'))
.findClosest('.viewsection_content');
}
}

class ProfileSettingsPage {
Expand Down Expand Up @@ -339,4 +468,30 @@ export interface PageWidgetPickerOptions {
dontAdd?: boolean;
/** If true, dismiss any tooltips that are shown. */
dismissTips?: boolean;
}
}

export interface IColsSelect<T = WebElement> {
cols: Array<number|string>;
rowNums: number[];
section?: string|WebElement;
mapper?: (e: WebElement) => Promise<T>;
}

export interface IColSelect<T = WebElement> {
col: number|string;
rowNums: number[];
section?: string|WebElement;
mapper?: (e: WebElement) => Promise<T>;
}

export interface ICellSelect {
col: number|string;
rowNum: number;
section?: string|WebElement;
}

export function exactMatch(value: string, flags?: string): RegExp {
return new RegExp(`^${escapeRegExp(value)}$`, flags);
}


2 changes: 1 addition & 1 deletion test/init-mocha-webdriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ if (process.env.MOCHA_WEBDRIVER_NO_CONTROL_BANNER === undefined) {
exports.mochaHooks = getMochaHooks();

let server: GristTestServer;
exports.mochaGlobalSetup = async function() {
exports.mochaGlobalSetup = async function() {
JakubSerafin marked this conversation as resolved.
Show resolved Hide resolved
server = new GristTestServer();
await server.start();
};
Expand Down
Loading
Loading