├── .gitignore
├── .prettierrc.json
├── dist
├── pipe.d.ts
├── map.d.ts.map
├── pipe.d.ts.map
├── filter.d.ts.map
├── really.d.ts.map
├── filter.d.ts
├── invoke.d.ts.map
├── pipe.js
├── map.d.ts
├── really.d.ts
├── invoke.d.ts
├── filter.js
├── index.d.ts.map
├── map.js
├── invoke.js
├── really.js
├── index.d.ts
└── index.js
├── src
├── pipe.ts
├── filter.ts
├── map.ts
├── invoke.ts
├── really.ts
└── index.ts
├── cypress
└── e2e
│ ├── json-attribute
│ ├── index.html
│ └── spec.js
│ ├── filter-by-value
│ ├── index.html
│ └── spec.js
│ ├── duplicates
│ ├── index.html
│ └── spec.js
│ ├── children
│ ├── index.html
│ └── children-spec.js
│ ├── object-spec.js
│ ├── parse-search-spec.js
│ ├── tap-spec.js
│ ├── table-spec.js
│ ├── table
│ ├── app.js
│ ├── app.css
│ ├── index.html
│ └── tableManager.js
│ └── invoke-spec.js
├── cypress.config.js
├── renovate.json
├── .github
└── workflows
│ ├── ci.yml
│ └── badges.yml
├── LICENSE
├── package.json
├── README.md
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 2,
4 | "semi": false,
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/dist/pipe.d.ts:
--------------------------------------------------------------------------------
1 | export declare function pipe(...fns: Function[]): (value: unknown) => unknown;
2 | //# sourceMappingURL=pipe.d.ts.map
--------------------------------------------------------------------------------
/src/pipe.ts:
--------------------------------------------------------------------------------
1 | export function pipe(...fns: Function[]) {
2 | return function (value: unknown) {
3 | return fns.reduce((acc, fn) => fn(acc), value)
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/cypress/e2e/json-attribute/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | First person
4 |
5 |
6 |
--------------------------------------------------------------------------------
/dist/map.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../src/map.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,EAAE,EAAE,QAAQ,UACP,OAAO,SAQ/B"}
--------------------------------------------------------------------------------
/dist/pipe.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"pipe.d.ts","sourceRoot":"","sources":["../src/pipe.ts"],"names":[],"mappings":"AAAA,wBAAgB,IAAI,CAAC,GAAG,GAAG,EAAE,QAAQ,EAAE,WACb,OAAO,aAGhC"}
--------------------------------------------------------------------------------
/dist/filter.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../src/filter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,MAAM,CAAC,SAAS,EAAE,QAAQ,UACjB,OAAO,SAW/B"}
--------------------------------------------------------------------------------
/dist/really.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"really.d.ts","sourceRoot":"","sources":["../src/really.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,GAAG,qBAAqB,EAAE,OAAO,EAAE,WAkBhC,OAAO,SAahC"}
--------------------------------------------------------------------------------
/cypress/e2e/filter-by-value/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/dist/filter.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Filter the values by the given predicate function.
3 | * @param {Function} predicate
4 | */
5 | export declare function filter(predicate: Function): (list: unknown) => any;
6 | //# sourceMappingURL=filter.d.ts.map
--------------------------------------------------------------------------------
/dist/invoke.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"invoke.d.ts","sourceRoot":"","sources":["../src/invoke.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,UACpC,OAAO,GAAG,OAAO,EAAE,SAmB3C"}
--------------------------------------------------------------------------------
/cypress/e2e/duplicates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Apples
5 | - Oranges
6 | - Bananas
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/dist/pipe.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.pipe = void 0;
4 | function pipe(...fns) {
5 | return function (value) {
6 | return fns.reduce((acc, fn) => fn(acc), value);
7 | };
8 | }
9 | exports.pipe = pipe;
10 |
--------------------------------------------------------------------------------
/cypress.config.js:
--------------------------------------------------------------------------------
1 | const { defineConfig } = require('cypress')
2 |
3 | module.exports = defineConfig({
4 | fixturesFolder: false,
5 | viewportHeight: 1100,
6 | e2e: {
7 | setupNodeEvents(on, config) {},
8 | supportFile: false,
9 | specPattern: 'cypress/e2e/**/*spec.js',
10 | },
11 | })
12 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["config:base"],
3 | "automerge": true,
4 | "prHourlyLimit": 2,
5 | "updateNotScheduled": false,
6 | "timezone": "America/New_York",
7 | "schedule": ["after 10pm and before 5am on every weekday", "every weekend"],
8 | "masterIssue": true,
9 | "labels": ["type: dependencies", "renovate"]
10 | }
11 |
--------------------------------------------------------------------------------
/dist/map.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Transforms an object or a list of objects using the supplied function or name of the property.
3 | * @param {Function} fn Function to apply to each object
4 | * @returns {Object|Array} Transformed value
5 | * @example cy.get('.todo').then(map('innerText'))
6 | */
7 | export declare function map(fn: Function): (list: unknown) => any;
8 | //# sourceMappingURL=map.d.ts.map
--------------------------------------------------------------------------------
/dist/really.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Constructs a "should(callback)" function on the fly from a pipeline
3 | * of individual functions to be called
4 | * @example cy.get(...).should(really(...))
5 | * @see https://github.com/bahmutov/cypress-should-really
6 | * @returns Function
7 | */
8 | export declare function really(...functionsAndAssertion: unknown[]): (value: unknown) => any;
9 | //# sourceMappingURL=really.d.ts.map
--------------------------------------------------------------------------------
/dist/invoke.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Invokes the given name (with optional arguments) on the given object.
3 | * @param {String} methodName
4 | * @param {...any} args
5 | * @returns Result of the method invocation
6 | * @example
7 | * cy.get('dates')
8 | * .then(map('innerText'))
9 | * .then(toDate)
10 | * .then(invoke('getTime'))
11 | */
12 | export declare function invoke(methodName: string, ...args: unknown[]): (list: unknown | unknown[]) => any;
13 | //# sourceMappingURL=invoke.d.ts.map
--------------------------------------------------------------------------------
/cypress/e2e/children/index.html:
--------------------------------------------------------------------------------
1 |
2 | Entire parent element is overwritten
3 |
6 |
17 |
18 |
--------------------------------------------------------------------------------
/src/filter.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Filter the values by the given predicate function.
3 | * @param {Function} predicate
4 | */
5 | export function filter(predicate: Function) {
6 | return function (list: unknown) {
7 | if (Cypress._.isArrayLike(list)) {
8 | const callbackFn =
9 | typeof predicate === 'function'
10 | ? (x: unknown) => predicate(x)
11 | : predicate
12 | return Cypress._.filter(list, callbackFn)
13 | } else {
14 | return predicate(list)
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/cypress/e2e/object-spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { really, its } from '../../src'
4 |
5 | describe('Assertion helpers', () => {
6 | it('works with object props', () => {
7 | const p = {
8 | person: {
9 | name: 'Joe',
10 | age: 42,
11 | },
12 | }
13 | setTimeout(() => {
14 | p.person.age = 90
15 | }, 1000)
16 | cy.wrap(p).should(really(its('person.age'), 'equal', 90))
17 | // using custom ".map" child command
18 | // cy.wrap(p).map('person.age').should('eq', 90)
19 | })
20 | })
21 |
--------------------------------------------------------------------------------
/src/map.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Transforms an object or a list of objects using the supplied function or name of the property.
3 | * @param {Function} fn Function to apply to each object
4 | * @returns {Object|Array} Transformed value
5 | * @example cy.get('.todo').then(map('innerText'))
6 | */
7 | export function map(fn: Function) {
8 | return function (list: unknown) {
9 | if (Cypress._.isArrayLike(list)) {
10 | const callbackFn = typeof fn === 'function' ? (x: unknown) => fn(x) : fn
11 | return Cypress._.map(list, callbackFn)
12 | } else {
13 | return fn(list)
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/cypress/e2e/json-attribute/spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { really, invoke, its } from '../../..'
4 |
5 | // implementation similar to "Json data attribute" recipe
6 | // from https://glebbahmutov.com/cypress-examples
7 |
8 | it('gets the parsed data attribute value', () => {
9 | cy.visit('cypress/e2e/json-attribute/index.html')
10 | // grab the element's attribute "data-field"
11 | // convert it into a JSON object
12 | // and grab its "age" property -> should be equal 10
13 | cy.get('#person').should(
14 | really(invoke('attr', 'data-field'), JSON.parse, its('age'), 'equal', 10),
15 | )
16 | })
17 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 | on: push
3 | jobs:
4 | test:
5 | runs-on: ubuntu-20.04
6 | steps:
7 | - name: Checkout 🛎
8 | uses: actions/checkout@v3
9 |
10 | - name: Run tests 🧪
11 | # https://github.com/cypress-io/github-action
12 | uses: cypress-io/github-action@v4
13 | with:
14 | build: npm run stop-only && npm run build
15 |
16 | - name: Semantic Release 🚀
17 | uses: cycjimmy/semantic-release-action@v3
18 | with:
19 | branch: main
20 | env:
21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
23 |
--------------------------------------------------------------------------------
/dist/filter.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.filter = void 0;
4 | /**
5 | * Filter the values by the given predicate function.
6 | * @param {Function} predicate
7 | */
8 | function filter(predicate) {
9 | return function (list) {
10 | if (Cypress._.isArrayLike(list)) {
11 | const callbackFn = typeof predicate === 'function'
12 | ? (x) => predicate(x)
13 | : predicate;
14 | return Cypress._.filter(list, callbackFn);
15 | }
16 | else {
17 | return predicate(list);
18 | }
19 | };
20 | }
21 | exports.filter = filter;
22 |
--------------------------------------------------------------------------------
/.github/workflows/badges.yml:
--------------------------------------------------------------------------------
1 | name: badges
2 | on:
3 | schedule:
4 | # update badges every night
5 | # because we have a few badges that are linked
6 | # to the external repositories
7 | - cron: '0 3 * * *'
8 |
9 | jobs:
10 | badges:
11 | name: Badges
12 | runs-on: ubuntu-20.04
13 | steps:
14 | - name: Checkout 🛎
15 | uses: actions/checkout@v3
16 |
17 | - name: Update version badges 🏷
18 | run: npx -p dependency-version-badge update-badge cypress
19 |
20 | - name: Commit any changed files 💾
21 | uses: stefanzweifel/git-auto-commit-action@v4
22 | with:
23 | commit_message: Updated badges
24 | branch: main
25 | file_pattern: README.md
26 |
--------------------------------------------------------------------------------
/dist/index.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,OACV,MAAM,aAG3B;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,OACf,MAAM,aAG3B;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,aAAa,EAAE,GAAG,iBACV,GAAG,aAGlC;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,OACvB,OAAO,KAAK,OAAO,SAGxC;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,QAE/B;AAED;;;;;;GAMG;AACH,wBAAgB,GAAG,CAAC,EAAE,EAAE,QAAQ,OACV,OAAO,aAI5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,OAE/C;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,WAAW,EAAE,QAAQ,SACvB,OAAO,SAI9B;AAED,cAAc,QAAQ,CAAA;AACtB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA"}
--------------------------------------------------------------------------------
/dist/map.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.map = void 0;
4 | /**
5 | * Transforms an object or a list of objects using the supplied function or name of the property.
6 | * @param {Function} fn Function to apply to each object
7 | * @returns {Object|Array} Transformed value
8 | * @example cy.get('.todo').then(map('innerText'))
9 | */
10 | function map(fn) {
11 | return function (list) {
12 | if (Cypress._.isArrayLike(list)) {
13 | const callbackFn = typeof fn === 'function' ? (x) => fn(x) : fn;
14 | return Cypress._.map(list, callbackFn);
15 | }
16 | else {
17 | return fn(list);
18 | }
19 | };
20 | }
21 | exports.map = map;
22 |
--------------------------------------------------------------------------------
/cypress/e2e/parse-search-spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { construct, invoke, pipe, really } from '../../src'
4 |
5 | // reusable function for converting search params to an object
6 | const searchToPlain = pipe(
7 | // string like "?foo=bar&baz=qux"
8 | construct(URLSearchParams), // URLSearchParams
9 | invoke('entries'), // Iterable<[string, string]>
10 | Array.from, // Array<[string, string]>
11 | Cypress._.fromPairs, // { [key: string]: string })
12 | )
13 |
14 | it(
15 | 'parses the URL search part',
16 | { baseUrl: 'https://example.cypress.io/' },
17 | () => {
18 | // we assume the page does not redirect and does not lose the search part
19 | cy.visit('/commands/location.html?foo=bar&baz=qux')
20 | cy.location('search')
21 | .should('equal', '?foo=bar&baz=qux')
22 | .and(
23 | really(searchToPlain, 'deep.equal', {
24 | foo: 'bar',
25 | baz: 'qux',
26 | }),
27 | )
28 | },
29 | )
30 |
--------------------------------------------------------------------------------
/cypress/e2e/tap-spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { tap, really, its } from '../../src'
4 |
5 | describe('tap', () => {
6 | it('calls the given function', () => {
7 | const stub = cy.stub().returns(2)
8 | cy.wrap(1)
9 | .then(tap(stub))
10 | .should('equal', 1)
11 | .then(() => {
12 | expect(stub).to.be.calledOnceWith(1)
13 | })
14 | })
15 |
16 | it('helps during debugging', () => {
17 | const stub = cy.stub(console, 'log')
18 | const o = {
19 | name: 'Joe',
20 | }
21 | cy.wrap(o)
22 | .should(really(its('name'), tap(console.log), 'equal', 'Mary'))
23 | .then(() => {
24 | expect(stub).to.be.calledWith('Joe') // was called multiple times
25 | expect(stub).to.be.calledWith('Mary') // was called just once before the test passes
26 | })
27 | // change the name to Mary after some time
28 | setTimeout(() => {
29 | o.name = 'Mary'
30 | }, 1000)
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/src/invoke.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Invokes the given name (with optional arguments) on the given object.
3 | * @param {String} methodName
4 | * @param {...any} args
5 | * @returns Result of the method invocation
6 | * @example
7 | * cy.get('dates')
8 | * .then(map('innerText'))
9 | * .then(toDate)
10 | * .then(invoke('getTime'))
11 | */
12 | export function invoke(methodName: string, ...args: unknown[]) {
13 | return function (list: unknown | unknown[]) {
14 | if (arguments.length > 1) {
15 | // the user tried to pass extra arguments with the list/object
16 | // that is a mistake!
17 | throw new Error(`Call to "${methodName}" must have a single argument`)
18 | }
19 |
20 | // @ts-ignore
21 | if (typeof list[methodName] === 'function') {
22 | // @ts-ignore
23 | return list[methodName](...args)
24 | }
25 |
26 | if (Cypress._.isArrayLike(list)) {
27 | return Cypress._.invokeMap(list, methodName, ...args)
28 | } else {
29 | return Cypress._.invoke(list, methodName, ...args)
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 bahmutov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/cypress/e2e/table-spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // @ts-ignore
4 | import { really, map, invoke, toDate } from '../..'
5 | chai.use(require('chai-sorted'))
6 |
7 | describe('Assertion helpers', () => {
8 | beforeEach(() => {
9 | cy.visit('/cypress/e2e/table')
10 | cy.get('#numrows').select('100')
11 | // check if the table fits into one page
12 | cy.get('.pagecontroller-num').should('have.length', 1)
13 | })
14 |
15 | it('map elements', () => {
16 | // sort by clicking the header column
17 | cy.contains('.sorterHeader', 'Points').click()
18 | cy.get('tbody td + td + td + td + td').should(
19 | really(map('innerText'), map(parseFloat), 'be.ascending'),
20 | )
21 | })
22 |
23 | it('map by date', () => {
24 | cy.contains('.sorterHeader', 'Date').click()
25 |
26 | // just to see what the steps produce
27 | // cy.get('tbody td:nth-child(4)')
28 | // .then(map('innerText'))
29 | // .then(map(toDate))
30 | // .then(invoke('getTime'))
31 | // .then(cy.log)
32 |
33 | cy.get('tbody td:nth-child(4)').should(
34 | really(map('innerText'), map(toDate), invoke('getTime'), 'be.ascending'),
35 | )
36 | })
37 | })
38 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cypress-should-really",
3 | "version": "0.0.0-development",
4 | "description": "Functional helpers for constructing Cypress should callbacks",
5 | "main": "dist",
6 | "types": "dist/index.d.ts",
7 | "files": [
8 | "src",
9 | "dist"
10 | ],
11 | "scripts": {
12 | "test": "cypress run",
13 | "semantic-release": "semantic-release",
14 | "stop-only": "stop-only --folder cypress/e2e",
15 | "build": "tsc -b",
16 | "watch": "tsc -w",
17 | "lint": "tsc --noEmit"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "https://github.com/bahmutov/cypress-should-really.git"
22 | },
23 | "keywords": [
24 | "cypress-plugin"
25 | ],
26 | "author": "Gleb Bahmutov ",
27 | "license": "MIT",
28 | "bugs": {
29 | "url": "https://github.com/bahmutov/cypress-should-really/issues"
30 | },
31 | "homepage": "https://github.com/bahmutov/cypress-should-really#readme",
32 | "devDependencies": {
33 | "chai-sorted": "0.2.0",
34 | "cypress": "^12.1.0",
35 | "prettier": "2.8.4",
36 | "semantic-release": "20.1.1",
37 | "stop-only": "3.3.1",
38 | "typescript": "^4.9.3"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/dist/invoke.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.invoke = void 0;
4 | /**
5 | * Invokes the given name (with optional arguments) on the given object.
6 | * @param {String} methodName
7 | * @param {...any} args
8 | * @returns Result of the method invocation
9 | * @example
10 | * cy.get('dates')
11 | * .then(map('innerText'))
12 | * .then(toDate)
13 | * .then(invoke('getTime'))
14 | */
15 | function invoke(methodName, ...args) {
16 | return function (list) {
17 | if (arguments.length > 1) {
18 | // the user tried to pass extra arguments with the list/object
19 | // that is a mistake!
20 | throw new Error(`Call to "${methodName}" must have a single argument`);
21 | }
22 | // @ts-ignore
23 | if (typeof list[methodName] === 'function') {
24 | // @ts-ignore
25 | return list[methodName](...args);
26 | }
27 | if (Cypress._.isArrayLike(list)) {
28 | return Cypress._.invokeMap(list, methodName, ...args);
29 | }
30 | else {
31 | return Cypress._.invoke(list, methodName, ...args);
32 | }
33 | };
34 | }
35 | exports.invoke = invoke;
36 |
--------------------------------------------------------------------------------
/cypress/e2e/table/app.js:
--------------------------------------------------------------------------------
1 | // generate random but consistent users
2 | // https://github.com/Marak/faker.js#setting-a-randomness-seed
3 | const users = []
4 |
5 | faker.seed(123)
6 | for (let i = 0; i < 23; i++) {
7 | users.push({
8 | id: faker.random.number(1000),
9 | firstName: faker.name.firstName(),
10 | lastName: faker.name.lastName(),
11 | date: faker.date.past().toISOString().split('T')[0],
12 | points: faker.random.number(99),
13 | })
14 | }
15 | // pad the numbers with leading zeros
16 | // because the sorting is alphanumeric only, does not work with numbers
17 | const rows = users
18 | .map((user) => {
19 | return `
20 |
21 | | ${String(user.id).padStart(4, '0')} |
22 | ${user.firstName} |
23 | ${user.lastName} |
24 | ${user.date} |
25 | ${String(user.points).padStart(2, '0')} |
26 |
27 | `
28 | })
29 | .join('\n')
30 |
31 | $('.tablemanager tbody').html(rows)
32 |
33 | $('.tablemanager').tablemanager({
34 | firstSort: [
35 | [3, 0],
36 | [2, 0],
37 | [1, 'asc'],
38 | ],
39 | disable: [],
40 | appendFilterby: true,
41 | dateFormat: [[3, 'yyyy-mm-dd']],
42 | debug: false,
43 | vocabulary: {
44 | voc_filter_by: 'Filter By',
45 | voc_type_here_filter: 'Filter...',
46 | voc_show_rows: 'Rows Per Page',
47 | },
48 | pagination: true,
49 | showrows: [5, 10, 20, 50, 100],
50 | disableFilterBy: [1],
51 | })
52 |
--------------------------------------------------------------------------------
/cypress/e2e/table/app.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Roboto Condensed', Helvetica, sans-serif;
3 | background-color: #f7f7f7;
4 | }
5 | .container {
6 | margin: 150px auto;
7 | max-width: 960px;
8 | }
9 | a {
10 | text-decoration: none;
11 | }
12 | table {
13 | width: 100%;
14 | border-collapse: collapse;
15 | margin-top: 20px;
16 | margin-bottom: 20px;
17 | }
18 | table,
19 | th,
20 | td {
21 | border: 1px solid #bbb;
22 | text-align: left;
23 | }
24 | tr:nth-child(even) {
25 | background-color: #f2f2f2;
26 | }
27 | th {
28 | background-color: #ddd;
29 | }
30 | th,
31 | td {
32 | padding: 5px;
33 | }
34 | button {
35 | cursor: pointer;
36 | }
37 | /*Initial style sort*/
38 | .tablemanager th.sorterHeader {
39 | cursor: pointer;
40 | }
41 | .tablemanager th.sorterHeader:after {
42 | content: ' \f0dc';
43 | font-family: 'FontAwesome';
44 | }
45 | /*Style sort desc*/
46 | .tablemanager th.sortingDesc:after {
47 | content: ' \f0dd';
48 | font-family: 'FontAwesome';
49 | }
50 | /*Style sort asc*/
51 | .tablemanager th.sortingAsc:after {
52 | content: ' \f0de';
53 | font-family: 'FontAwesome';
54 | }
55 | /*Style disabled*/
56 | .tablemanager th.disableSort {
57 | }
58 | #for_numrows {
59 | padding: 10px;
60 | float: left;
61 | }
62 | #for_filter_by {
63 | padding: 10px;
64 | float: right;
65 | }
66 | #pagesControllers {
67 | display: block;
68 | text-align: center;
69 | }
70 | .currentPage {
71 | font-weight: bold;
72 | background-color: #fff;
73 | }
74 |
--------------------------------------------------------------------------------
/src/really.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { pipe } from './pipe'
4 |
5 | /**
6 | * Constructs a "should(callback)" function on the fly from a pipeline
7 | * of individual functions to be called
8 | * @example cy.get(...).should(really(...))
9 | * @see https://github.com/bahmutov/cypress-should-really
10 | * @returns Function
11 | */
12 | export function really(...functionsAndAssertion: unknown[]) {
13 | if (!arguments.length) {
14 | throw new Error('really() needs arguments really badly')
15 | }
16 |
17 | const fns = Cypress._.takeWhile(arguments, (arg) => typeof arg === 'function')
18 | const chainerIndex = Cypress._.findIndex(
19 | arguments,
20 | (arg) => typeof arg === 'string',
21 | )
22 | if (chainerIndex === -1) {
23 | throw new Error('sh: no chainer found')
24 | }
25 | const chainer = arguments[chainerIndex]
26 | const chainerArguments = Cypress._.slice(arguments, chainerIndex + 1)
27 | const chainers = chainer.split('.')
28 | const fn = pipe(...fns)
29 |
30 | return function (value: unknown) {
31 | // console.log('value', value)
32 | const transformed = fn(value)
33 | // console.log('transformed', transformed)
34 | return chainers.reduce((acc: any, chainer: string) => {
35 | const currentChainer = acc[chainer]
36 | if (typeof currentChainer === 'function') {
37 | return acc[chainer](...chainerArguments)
38 | } else {
39 | return acc[chainer]
40 | }
41 | }, expect(transformed).to)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/cypress/e2e/filter-by-value/spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // normally you would import from "cypress-should-really"
4 | import { really, map, its, filter, isEqual } from '../../..'
5 |
6 | // find the explanation in the video
7 | // "Filter Input Elements By Value Using cypress-should-really Plugin"
8 | // https://youtu.be/Gxoo6uZMo9I
9 |
10 | it(
11 | 'finds input elements with the current value "fox"',
12 | { viewportHeight: 200, viewportWidth: 200 },
13 | () => {
14 | cy.visit('cypress/e2e/filter-by-value/index.html')
15 | // change one of the inputs by typing "fox" into it
16 | cy.get('#i2').type('fox')
17 | // NOTE: only the elements with the markup attribute "value" are returned
18 | cy.get('#inputs input[value=fox]').should('have.length', 1)
19 |
20 | // instead filter the elements by their current value
21 | // first approach: use separate commands
22 | // not ideal, since only the cy.filter is retried
23 | // which loses the Cypress retry-ability
24 | // https://on.cypress.io/retry-ability
25 | cy.get('#inputs input')
26 | .filter((k, el) => {
27 | return el.value === 'fox'
28 | })
29 | // finds both input elements with the value "fox"
30 | .should('have.length', 2)
31 |
32 | // second approach: build up and use
33 | // a single custom "should(callback)"
34 | cy.get('#inputs input').should(
35 | // [jqueryElement]
36 | really(
37 | map(its('value')), // [string]
38 | filter(isEqual('fox')),
39 | 'have.length',
40 | 2,
41 | ),
42 | )
43 | },
44 | )
45 |
--------------------------------------------------------------------------------
/dist/really.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | ///
3 | Object.defineProperty(exports, "__esModule", { value: true });
4 | exports.really = void 0;
5 | const pipe_1 = require("./pipe");
6 | /**
7 | * Constructs a "should(callback)" function on the fly from a pipeline
8 | * of individual functions to be called
9 | * @example cy.get(...).should(really(...))
10 | * @see https://github.com/bahmutov/cypress-should-really
11 | * @returns Function
12 | */
13 | function really(...functionsAndAssertion) {
14 | if (!arguments.length) {
15 | throw new Error('really() needs arguments really badly');
16 | }
17 | const fns = Cypress._.takeWhile(arguments, (arg) => typeof arg === 'function');
18 | const chainerIndex = Cypress._.findIndex(arguments, (arg) => typeof arg === 'string');
19 | if (chainerIndex === -1) {
20 | throw new Error('sh: no chainer found');
21 | }
22 | const chainer = arguments[chainerIndex];
23 | const chainerArguments = Cypress._.slice(arguments, chainerIndex + 1);
24 | const chainers = chainer.split('.');
25 | const fn = (0, pipe_1.pipe)(...fns);
26 | return function (value) {
27 | // console.log('value', value)
28 | const transformed = fn(value);
29 | // console.log('transformed', transformed)
30 | return chainers.reduce((acc, chainer) => {
31 | const currentChainer = acc[chainer];
32 | if (typeof currentChainer === 'function') {
33 | return acc[chainer](...chainerArguments);
34 | }
35 | else {
36 | return acc[chainer];
37 | }
38 | }, expect(transformed).to);
39 | };
40 | }
41 | exports.really = really;
42 |
--------------------------------------------------------------------------------
/cypress/e2e/table/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 | Table Manager Plugin Example
11 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
34 |
35 |
36 |
37 |
38 |
39 | | ID |
40 | First Name |
41 | Last Name |
42 | Date |
43 | Points |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/cypress/e2e/duplicates/spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import {
4 | really,
5 | invoke,
6 | map,
7 | tap,
8 | greaterThan,
9 | partial,
10 | pipe,
11 | flipTwoArguments,
12 | } from '../../..'
13 | const { countBy, pickBy } = Cypress._
14 |
15 | describe(
16 | 'finding duplicates',
17 | { viewportHeight: 100, viewportWidth: 200 },
18 | () => {
19 | it('checks if an object is empty', () => {
20 | const p = pipe(countBy, (counts) => pickBy(counts, (n) => n > 1))
21 | const output = p(['a', 'b', 'c', 'a'])
22 | expect(output).to.be.deep.equal({ a: 2 })
23 | })
24 |
25 | it('by attribute (explicit)', () => {
26 | cy.visit('cypress/e2e/duplicates/index.html')
27 |
28 | cy.get('li').should(
29 | really(
30 | map(invoke('getAttribute', 'data-product-id')),
31 | countBy,
32 | (counts) => pickBy(counts, (n) => n > 1),
33 | // if you want to debug this pipeline of functions
34 | // use tap(console.log) function
35 | tap(console.log),
36 | 'be.empty',
37 | ),
38 | )
39 | })
40 |
41 | it('by attribute (greaterThan)', () => {
42 | cy.visit('cypress/e2e/duplicates/index.html')
43 |
44 | // using a few more shortcuts
45 | cy.get('li').should(
46 | really(
47 | map(invoke('getAttribute', 'data-product-id')),
48 | countBy,
49 | (counts) => pickBy(counts, greaterThan(1)),
50 | 'be.empty',
51 | ),
52 | )
53 | })
54 |
55 | it('by attribute (flip arguments and partial apply)', () => {
56 | cy.visit('cypress/e2e/duplicates/index.html')
57 | // modify the _.pickBy to take arguments in
58 | // the flipped order: (fn, array)
59 | // then we know the first argument - greaterThan(1) function
60 | // so we apply it right away. The returned function
61 | // is waiting for the object
62 | const pickLargerThanOne = partial(
63 | flipTwoArguments(pickBy),
64 | greaterThan(1),
65 | )
66 | cy.get('li').should(
67 | really(
68 | map(invoke('getAttribute', 'data-product-id')),
69 | countBy,
70 | pickLargerThanOne,
71 | 'be.empty',
72 | ),
73 | )
74 | })
75 | },
76 | )
77 |
--------------------------------------------------------------------------------
/cypress/e2e/invoke-spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { invoke, map, its, pipe } from '../..'
4 |
5 | describe('invoke', () => {
6 | it('works on arrays', () => {
7 | const strings = ['a', 'bb', 'ccc']
8 | const results = invoke('toUpperCase')(strings)
9 | expect(results).to.deep.equal(['A', 'BB', 'CCC'])
10 | })
11 |
12 | it('works on jQuery', () => {
13 | const $ = Cypress.$(`
14 |
15 | - a
16 | - bb
17 | - ccc
18 |
19 | `)
20 | const li = invoke('find', 'li')($)
21 | const strings = map('innerText')(li)
22 | expect(strings).to.deep.equal(['a', 'bb', 'ccc'])
23 |
24 | // note that calling "text()" on jQuery element
25 | // returns a single string
26 | cy.wrap($)
27 | .then(invoke('find', 'li'))
28 | .then(invoke('text'))
29 | .should('deep.equal', 'abbccc')
30 | })
31 |
32 | it('passes arguments', () => {
33 | const calc = {
34 | add(a, b) {
35 | return a + b
36 | },
37 | }
38 | const sum = invoke('add', 1, 2)(calc)
39 | expect(sum).to.equal(3)
40 | })
41 |
42 | it('throws an error if final call is not unary', () => {
43 | // command mistake - passing arguments to the final call
44 | // instead of preparing it with the name of the method
45 | expect(() => {
46 | invoke('something')({}, 1, 2)
47 | }).to.throw('Call to "something" must have a single argument')
48 | })
49 |
50 | it('can be combined with map', () => {
51 | const $ = Cypress.$(`
52 |
53 | - a
54 | - bb
55 | - ccc
56 |
57 | `)
58 | const li = invoke('find', 'li')($)
59 | // when we map, we get the regular DOM elements
60 | const strings = map(its('innerText'))(li)
61 | const upper = map(invoke('toUpperCase'))(strings)
62 | expect(upper).to.deep.equal(['A', 'BB', 'CCC'])
63 | })
64 |
65 | it('can be combined with pipe', () => {
66 | const $ = Cypress.$(`
67 |
68 | - a
69 | - bb
70 | - ccc
71 |
72 | `)
73 |
74 | const toStrings = pipe(invoke('find', 'li'), map('innerText'))
75 | const strings = toStrings($)
76 | expect(strings).to.deep.equal(['a', 'bb', 'ccc'])
77 | })
78 | })
79 |
--------------------------------------------------------------------------------
/cypress/e2e/children/children-spec.js:
--------------------------------------------------------------------------------
1 | import { really, invoke } from '../../..'
2 |
3 | describe(
4 | 'count children elements',
5 | { viewportHeight: 300, viewportWidth: 300 },
6 | () => {
7 | // fails because the entire parent element is replaced
8 | it.skip('finds the children elements even if the entire element is replaced', () => {
9 | cy.visit('cypress/e2e/children/index.html')
10 | cy.get('[data-cy=parent]')
11 | .should('be.visible')
12 | .contains('[data-cy=first]', 'First')
13 | .should('be.visible')
14 | })
15 |
16 | it('retries until the parent element has two children', () => {
17 | cy.visit('cypress/e2e/children/index.html')
18 | // once we confirm the parent element has two children with data-cy attribute
19 | // we can continue
20 | cy.get('[data-cy=parent] [data-cy]').should('have.length', 2)
21 | cy.get('[data-cy=parent]')
22 | .contains('[data-cy=first]', 'First')
23 | .should('be.visible')
24 | })
25 |
26 | it('finds the children after wait', () => {
27 | cy.visit('cypress/e2e/children/index.html')
28 | // let the page update itself including the parent element
29 | .wait(2000)
30 | cy.get('[data-cy=parent]')
31 | .invoke('find', '[data-cy]')
32 | .should('have.length', 2)
33 | })
34 |
35 | it('really retries until the parent element has two children', () => {
36 | cy.visit('cypress/e2e/children/index.html')
37 | cy.get('[data-cy=parent]')
38 | .should(really(invoke('find', '[data-cy]'), 'have.length', 2))
39 | .contains('[data-cy=first]', 'First')
40 | })
41 |
42 | it('really finds a child element with text', () => {
43 | cy.visit('cypress/e2e/children/index.html')
44 | cy.get('[data-cy=parent]')
45 | // find the element with text "Second"
46 | // should exist
47 | .should(really(invoke('find', ':contains(Second)'), 'exist'))
48 | .contains('[data-cy=second]', 'Second')
49 | })
50 |
51 | it('really finds text inside', () => {
52 | cy.visit('cypress/e2e/children/index.html')
53 | cy.get('[data-cy=parent]')
54 | // take the text inside the element
55 | // find the word "Second"
56 | // should be true
57 | .should(really(invoke('text'), invoke('includes', 'Second'), 'be.true'))
58 | .contains('[data-cy=second]', 'Second')
59 | })
60 | },
61 | )
62 |
--------------------------------------------------------------------------------
/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Grabs a property or a nested path from the given object.
3 | * @param {String} path
4 | * @returns Value of the property
5 | * @example
6 | * cy.wrap({ foo: 'bar' }).then(its('foo'))
7 | */
8 | export declare function its(path: string): (o: object) => unknown;
9 | /**
10 | * Curried > N function
11 | * @param {number} n
12 | * @returns Boolean
13 | * @example
14 | * expect(greaterThan(10)(5)).to.be.false
15 | */
16 | export declare function greaterThan(n: number): (x: number) => boolean;
17 | /**
18 | * Curried deep comparison
19 | * @param {any} isEqual
20 | */
21 | export declare function isEqual(expectedValue: any): (actualValue: any) => boolean;
22 | /**
23 | * Takes a function and returns a function that expects the first two
24 | * arguments in the reverse order.
25 | * @param {Function} fn Function to call
26 | * @returns Function
27 | * @example
28 | * flipTwoArguments(Cypress._.map)(x => x * 2, [1, 2, 3])
29 | */
30 | export declare function flipTwoArguments(fn: Function): (a: unknown, b: unknown) => any;
31 | /**
32 | * Converts the given string into a JavaScript Date object
33 | * @param {String} s dateString
34 | * @returns {Date} Date instance
35 | * @deprecated Use "constructor(Date)" instead
36 | */
37 | export declare function toDate(s: string): Date;
38 | /**
39 | * Returns a function that waits for the argument, passes that argument
40 | * to the given callback, but returns the original value. Useful
41 | * for debugging data transformations.
42 | * @param {Function} fn
43 | * @example cyw.wrap(1).then(tap(console.log)).should('equal', 1)
44 | */
45 | export declare function tap(fn: Function): (x: unknown) => unknown;
46 | /**
47 | * Returns a function with the first argument bound.
48 | * @param {Function} fn Function to partially apply
49 | * @param {any} a First argument to apply
50 | * @example
51 | * const add = (a, b) => a + b
52 | * const addOne = partial(add, 1)
53 | * addOne(2) // 3
54 | */
55 | export declare function partial(fn: Function, a: unknown): any;
56 | /**
57 | * Given a constructor function, returns a function
58 | * that waits for a single argument before calling "new constructor(arg)"
59 | * @example constructor(Date)
60 | * @see https://glebbahmutov.com/blog/work-around-the-keyword-new-in-javascript/
61 | */
62 | export declare function construct(constructor: Function): (arg: unknown) => any;
63 | export * from './pipe';
64 | export * from './really';
65 | export * from './map';
66 | export * from './filter';
67 | export * from './invoke';
68 | //# sourceMappingURL=index.d.ts.map
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | /**
4 | * Grabs a property or a nested path from the given object.
5 | * @param {String} path
6 | * @returns Value of the property
7 | * @example
8 | * cy.wrap({ foo: 'bar' }).then(its('foo'))
9 | */
10 | export function its(path: string) {
11 | return function (o: object) {
12 | return Cypress._.property(path)(o)
13 | }
14 | }
15 |
16 | /**
17 | * Curried > N function
18 | * @param {number} n
19 | * @returns Boolean
20 | * @example
21 | * expect(greaterThan(10)(5)).to.be.false
22 | */
23 | export function greaterThan(n: number) {
24 | return function (x: number) {
25 | return x > n
26 | }
27 | }
28 |
29 | /**
30 | * Curried deep comparison
31 | * @param {any} isEqual
32 | */
33 | export function isEqual(expectedValue: any) {
34 | return function (actualValue: any) {
35 | return Cypress._.isEqual(actualValue, expectedValue)
36 | }
37 | }
38 |
39 | /**
40 | * Takes a function and returns a function that expects the first two
41 | * arguments in the reverse order.
42 | * @param {Function} fn Function to call
43 | * @returns Function
44 | * @example
45 | * flipTwoArguments(Cypress._.map)(x => x * 2, [1, 2, 3])
46 | */
47 | export function flipTwoArguments(fn: Function) {
48 | return function (a: unknown, b: unknown) {
49 | return fn(b, a)
50 | }
51 | }
52 |
53 | /**
54 | * Converts the given string into a JavaScript Date object
55 | * @param {String} s dateString
56 | * @returns {Date} Date instance
57 | * @deprecated Use "constructor(Date)" instead
58 | */
59 | export function toDate(s: string) {
60 | return new Date(s)
61 | }
62 |
63 | /**
64 | * Returns a function that waits for the argument, passes that argument
65 | * to the given callback, but returns the original value. Useful
66 | * for debugging data transformations.
67 | * @param {Function} fn
68 | * @example cyw.wrap(1).then(tap(console.log)).should('equal', 1)
69 | */
70 | export function tap(fn: Function) {
71 | return function (x: unknown) {
72 | fn(x)
73 | return x
74 | }
75 | }
76 |
77 | /**
78 | * Returns a function with the first argument bound.
79 | * @param {Function} fn Function to partially apply
80 | * @param {any} a First argument to apply
81 | * @example
82 | * const add = (a, b) => a + b
83 | * const addOne = partial(add, 1)
84 | * addOne(2) // 3
85 | */
86 | export function partial(fn: Function, a: unknown) {
87 | return fn.bind(null, a)
88 | }
89 |
90 | /**
91 | * Given a constructor function, returns a function
92 | * that waits for a single argument before calling "new constructor(arg)"
93 | * @example constructor(Date)
94 | * @see https://glebbahmutov.com/blog/work-around-the-keyword-new-in-javascript/
95 | */
96 | export function construct(constructor: Function) {
97 | return function (arg: unknown) {
98 | // @ts-ignore
99 | return new constructor(arg)
100 | }
101 | }
102 |
103 | export * from './pipe'
104 | export * from './really'
105 | export * from './map'
106 | export * from './filter'
107 | export * from './invoke'
108 |
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | ///
3 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4 | if (k2 === undefined) k2 = k;
5 | var desc = Object.getOwnPropertyDescriptor(m, k);
6 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7 | desc = { enumerable: true, get: function() { return m[k]; } };
8 | }
9 | Object.defineProperty(o, k2, desc);
10 | }) : (function(o, m, k, k2) {
11 | if (k2 === undefined) k2 = k;
12 | o[k2] = m[k];
13 | }));
14 | var __exportStar = (this && this.__exportStar) || function(m, exports) {
15 | for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
16 | };
17 | Object.defineProperty(exports, "__esModule", { value: true });
18 | exports.construct = exports.partial = exports.tap = exports.toDate = exports.flipTwoArguments = exports.isEqual = exports.greaterThan = exports.its = void 0;
19 | /**
20 | * Grabs a property or a nested path from the given object.
21 | * @param {String} path
22 | * @returns Value of the property
23 | * @example
24 | * cy.wrap({ foo: 'bar' }).then(its('foo'))
25 | */
26 | function its(path) {
27 | return function (o) {
28 | return Cypress._.property(path)(o);
29 | };
30 | }
31 | exports.its = its;
32 | /**
33 | * Curried > N function
34 | * @param {number} n
35 | * @returns Boolean
36 | * @example
37 | * expect(greaterThan(10)(5)).to.be.false
38 | */
39 | function greaterThan(n) {
40 | return function (x) {
41 | return x > n;
42 | };
43 | }
44 | exports.greaterThan = greaterThan;
45 | /**
46 | * Curried deep comparison
47 | * @param {any} isEqual
48 | */
49 | function isEqual(expectedValue) {
50 | return function (actualValue) {
51 | return Cypress._.isEqual(actualValue, expectedValue);
52 | };
53 | }
54 | exports.isEqual = isEqual;
55 | /**
56 | * Takes a function and returns a function that expects the first two
57 | * arguments in the reverse order.
58 | * @param {Function} fn Function to call
59 | * @returns Function
60 | * @example
61 | * flipTwoArguments(Cypress._.map)(x => x * 2, [1, 2, 3])
62 | */
63 | function flipTwoArguments(fn) {
64 | return function (a, b) {
65 | return fn(b, a);
66 | };
67 | }
68 | exports.flipTwoArguments = flipTwoArguments;
69 | /**
70 | * Converts the given string into a JavaScript Date object
71 | * @param {String} s dateString
72 | * @returns {Date} Date instance
73 | * @deprecated Use "constructor(Date)" instead
74 | */
75 | function toDate(s) {
76 | return new Date(s);
77 | }
78 | exports.toDate = toDate;
79 | /**
80 | * Returns a function that waits for the argument, passes that argument
81 | * to the given callback, but returns the original value. Useful
82 | * for debugging data transformations.
83 | * @param {Function} fn
84 | * @example cyw.wrap(1).then(tap(console.log)).should('equal', 1)
85 | */
86 | function tap(fn) {
87 | return function (x) {
88 | fn(x);
89 | return x;
90 | };
91 | }
92 | exports.tap = tap;
93 | /**
94 | * Returns a function with the first argument bound.
95 | * @param {Function} fn Function to partially apply
96 | * @param {any} a First argument to apply
97 | * @example
98 | * const add = (a, b) => a + b
99 | * const addOne = partial(add, 1)
100 | * addOne(2) // 3
101 | */
102 | function partial(fn, a) {
103 | return fn.bind(null, a);
104 | }
105 | exports.partial = partial;
106 | /**
107 | * Given a constructor function, returns a function
108 | * that waits for a single argument before calling "new constructor(arg)"
109 | * @example constructor(Date)
110 | * @see https://glebbahmutov.com/blog/work-around-the-keyword-new-in-javascript/
111 | */
112 | function construct(constructor) {
113 | return function (arg) {
114 | // @ts-ignore
115 | return new constructor(arg);
116 | };
117 | }
118 | exports.construct = construct;
119 | __exportStar(require("./pipe"), exports);
120 | __exportStar(require("./really"), exports);
121 | __exportStar(require("./map"), exports);
122 | __exportStar(require("./filter"), exports);
123 | __exportStar(require("./invoke"), exports);
124 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # cypress-should-really [](https://github.com/bahmutov/cypress-should-really/actions/workflows/ci.yml)  [![renovate-app badge][renovate-badge]][renovate-app]
2 |
3 | Read the blog posts [Functional Helpers For Cypress Tests](https://glebbahmutov.com/blog/fp-cy-helpers/) and [Check Items For Duplicates](https://glebbahmutov.com/blog/check-for-duplicates/). 🎓 Covered in my course [Cypress Plugins](https://cypress.tips/courses/cypress-plugins).
4 |
5 | ## Example
6 |
7 | Grab text from each list item, convert the strings into Dates, convert Dates into timestamps, then confirm they are sorted using [chai-sorted](https://www.chaijs.com/plugins/chai-sorted/) assertion.
8 |
9 | ```js
10 | import { innerText, toDate, invoke } from 'cypress-should-really'
11 | cy.get('.dates')
12 | .then(map('innerText'))
13 | .then(map(toDate))
14 | .then(invoke('getDate'))
15 | .should('be.sorted')
16 | ```
17 |
18 | The above separates each operation using [cy.then](https://on.cypress.io/then) commands, which are not retried. Luckily, we can easily combine the individual steps into a single data transformation function using the `pipe` command.
19 |
20 | ```js
21 | import { innerText, toDate, invoke, pipe } from 'cypress-should-really'
22 | const transform = pipe(map('innerText'), map(toDate), invoke('getDate'))
23 | cy.get('.dates').then(transform).should('be.sorted')
24 | ```
25 |
26 | The above commands are still NOT retrying the first `cy.get` command, thus if the page changes, the assertion still fails since it never "sees" the changed elements. We need to remove the `.then(transform)` step and directly tie the `cy.get` command to the assertion. We can move the data transformation into the assertion callback that transforms the data AND runs the assertion using `really` function.
27 |
28 | ```js
29 | import { innerText, toDate, invoke, pipe, really } from 'cypress-should-really'
30 | const transform = pipe(map('innerText'), map(toDate), invoke('getDate'))
31 | cy.get('.dates').should(really(transform, 'be.sorted'))
32 | ```
33 |
34 | Finally, we can skip using the `pipe` function, since it is built into the `really` automatically. All functions before the assertion are applied and then the assertion runs.
35 |
36 | ```js
37 | import { innerText, toDate, invoke, really } from 'cypress-should-really'
38 | cy.get('.dates').should(
39 | really(map('innerText'), map(toDate), invoke('getDate'), 'be.sorted'),
40 | )
41 | ```
42 |
43 | ## Installation
44 |
45 | ```text
46 | $ npm i -D cypress-should-really
47 | # or install using Yarn
48 | $ yarn add -D cypress-should-really
49 | ```
50 |
51 | ## API
52 |
53 | - `map`
54 | - `invoke`
55 | - `constructor`
56 | - `toDate` (deprecated) use `constructor(Date)` instead
57 | - `its`
58 | - `greaterThan`
59 | - `flipTwoArguments`
60 | - `partial`
61 | - `pipe`
62 | - `tap`
63 | - `filter`
64 | - `isEqual`
65 | - `really`
66 |
67 | ## invoke
68 |
69 | `invoke(, ...arguments)` returns a function that waits for an object or an array, then calls the method and returns the results
70 |
71 | ```js
72 | const calc = {
73 | add(a, b) {
74 | return a + b
75 | },
76 | }
77 | invoke('add', 1, 2)(calc)
78 | // 3
79 | ```
80 |
81 | See [invoke-spec.js](./cypress/e2e/invoke-spec.js)
82 |
83 | ## constructor
84 |
85 | Takes a constructor function, returns a function that waits for a single argument and calls with the `new` keyword.
86 |
87 | ```js
88 | import {constructor} from 'cypress-should-really'
89 | // converts a string to Date object
90 | .then(constructor(Date))
91 | ```
92 |
93 | ## tap
94 |
95 | Passes the argument into the given function, but returns the original argument. Useful for debugging pipes of functions - insert it in every place of the pipeline to see the values.
96 |
97 | ```js
98 | const o = {
99 | name: 'Joe',
100 | }
101 | cy.wrap(o).should(really(its('name'), tap(console.log), 'equal', 'Mary'))
102 | // change the name to Mary after some time
103 | setTimeout(() => {
104 | o.name = 'Mary'
105 | }, 1000)
106 | ```
107 |
108 | In the above example, the `console.log` the string "Joe" multiple times, before logging "Mary" once and passing the test.
109 |
110 | See [tap-spec.js](./cypress/e2e/tap-spec.js)
111 |
112 | ## Videos
113 |
114 | - [Filter Input Elements By Value Using cypress-should-really Plugin](https://youtu.be/Gxoo6uZMo9I)
115 |
116 | ## See also
117 |
118 | - [cypress-recurse](https://github.com/bahmutov/cypress-recurse)
119 | - [cypress-map](https://github.com/bahmutov/cypress-map) for Cypress v12+
120 |
121 | ## Small print
122 |
123 | Author: Gleb Bahmutov <gleb.bahmutov@gmail.com> © 2021
124 |
125 | - [@bahmutov](https://twitter.com/bahmutov)
126 | - [glebbahmutov.com](https://glebbahmutov.com)
127 | - [blog](https://glebbahmutov.com/blog)
128 | - [videos](https://www.youtube.com/glebbahmutov)
129 | - [presentations](https://slides.com/bahmutov)
130 | - [cypress.tips](https://cypress.tips)
131 |
132 | License: MIT - do anything with the code, but don't blame me if it does not work.
133 |
134 | Support: if you find any problems with this module, email / tweet /
135 | [open issue](https://github.com/bahmutov/cypress-should-really/issues) on Github
136 |
137 | [renovate-badge]: https://img.shields.io/badge/renovate-app-blue.svg
138 | [renovate-app]: https://renovateapp.com/
139 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
15 | "lib": [
16 | "DOM",
17 | "ESNext"
18 | ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
19 | // "jsx": "preserve", /* Specify what JSX code is generated. */
20 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
21 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
22 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
23 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
24 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
25 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
26 | // "noLib": true /* Disable including any library files, including the default lib.d.ts. */,
27 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
28 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
29 |
30 | /* Modules */
31 | "module": "commonjs" /* Specify what module code is generated. */,
32 | // "rootDir": "./", /* Specify the root folder within your source files. */
33 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
34 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
35 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
36 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
37 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
38 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
39 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
40 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
41 | // "resolveJsonModule": true, /* Enable importing .json files. */
42 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
43 |
44 | /* JavaScript Support */
45 | "allowJs": false /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */,
46 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
47 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
48 |
49 | /* Emit */
50 | "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,
51 | "declarationMap": true /* Create sourcemaps for d.ts files. */,
52 | "emitDeclarationOnly": false /* Only output d.ts files and not JavaScript files. */,
53 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
54 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
55 | "outDir": "./dist" /* Specify an output folder for all emitted files. */,
56 | // "removeComments": true, /* Disable emitting comments. */
57 | // "noEmit": true, /* Disable emitting files from a compilation. */
58 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
59 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
60 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
61 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
62 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
63 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
64 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
65 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
66 | // "newLine": "crlf", /* Set the newline character for emitting files. */
67 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
68 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
69 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
70 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
71 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
72 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
73 |
74 | /* Interop Constraints */
75 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
76 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
77 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
78 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
79 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
80 |
81 | /* Type Checking */
82 | "strict": true /* Enable all strict type-checking options. */,
83 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
84 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
85 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
86 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
87 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
88 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
89 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
90 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
91 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
92 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
93 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
94 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
95 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
96 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
97 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
98 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
99 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
100 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
101 |
102 | /* Completeness */
103 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
104 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
105 | },
106 | "include": ["./src/index.ts"]
107 | }
108 |
--------------------------------------------------------------------------------
/cypress/e2e/table/tableManager.js:
--------------------------------------------------------------------------------
1 | /**
2 |
3 | jQuery Plugin
4 | Name: tableManager
5 | Version: 1.1.0
6 | Author: Pietrantonio Alessandro (Stone)
7 | Author's website: http://www.stonewebdesign.it
8 |
9 | @-- What's new --@
10 |
11 | 1)
12 | New classes to the table: tablePagination, tableFilterBy;
13 |
14 | 2)
15 | New class to the th element: disableFilterBy;
16 |
17 | 3)
18 | New option to disable filter on one or more column: disableFilterBy: []
19 |
20 | @-- Usage --@
21 |
22 | Important! This plugin NEED jQuery library to work. As a matter of fact it's a jQuery plugin.
23 |
24 | Minimum requirements:
25 | - jQuery library
26 |
27 | 1)
28 | To include jQuery library download it from jQuery site and put the .js file into your site folders, then put this line into your document:
29 |
30 | Alternatively you can include it without download it, just like this:
31 |
32 | 2)
33 | Include this plugin just like this: download it and put the .js file into your site folders, then write this line into your document:
34 |
35 | Important! REMEMBER: THIS LINE HAVE TO BE AFTER JQUERY INCLUDING LINE!
36 |
37 |
38 | If you want you can customize this plugin as you prefer and need just with the following options.
39 | Options:
40 |
41 | debug = (boolean) can be true or false or not set. It activates debug mode and show messages into browser console.
42 |
43 | firstSort = can be an array of integer, or just a single integer. The first parameter determines the number of column to start sorting, the second parameter determines the order (asc or 0 = ascending, desc or 1 = descending). Ex.:
44 | firstSort : [[1,'asc']] --> the table is sorted by first column (first parameter = 1), by ascending order (second parameter = 0).
45 |
46 | disable = this option is used to disable one or more columns and expect one parameter per column. The parameter can be an integer or, to disable last column, -1 or the word "last". Ex.:
47 | disable : [3] --> disable sorting on third column
48 |
49 | appendFilterby = (boolean) used to add a filter on top of the table. The filter will be composed by one select to select by which column to filter and an input text to filter typing. Ex.:
50 | appendFilterby : true
51 |
52 | dateFormat = used to indicate column and dateformat. It helps to sort by date which is formatted like dd-mm-yyyy or mmddyyyy. The first parameter is the column, the second parameter is date format. Ex.:
53 | dateFormat : [[3, 'dd-mm-yyyy']] --> the third column is date and it's formatted like dd-mm-yyyy
54 |
55 | pagination = true or false or not set. It permits to paginate table and append controllers under the table. Ex.:
56 | pagination : true --> enable pagination tool
57 |
58 | showrows = you can append to table a select by which you can select number of rows to show. It must be an array of numbers. Ex.:
59 | showrows : [10,100,1000] --> you can choose to show 10, 100, 1000 rows
60 |
61 | vocabulary = used to translate labels. The following are accepted labels:
62 | voc_filter_by
63 | voc_type_here_filter
64 |
65 | disableFilterBy = used to disable filter by specific columns. It must be an array of numbers. To disable filter by last column you can use "last". Ex.:
66 | disableFilterBy: [1, "last"] --> it disable filter by first and last column
67 |
68 | Classes:
69 |
70 | (th) disableSort = disable that specific column
71 | disableFilterBy = disable filter for that specific column
72 |
73 | (table) tablePagination = append pagination elements to table
74 | tableFilterBy = append filter tool to table
75 |
76 | Data- attributes:
77 |
78 | data-tablemanager =
79 | 'disable' --> disable that specific column
80 |
81 | '{"dateFormat":"dd-mm-yyyy"}' --> this secific column represent a date with the forma tdd-mm-yyyy (Important! To pass correctly this data attribute the attribute value has to be written between '' and attribute single elements like dateFormat or mm-dd-yyyy between "")
82 |
83 |
84 | Important! Do not edit this plugin if you're not sure you're doing it right. The Author is not responsible for any malfunctions caused by the end User.
85 |
86 | **/
87 |
88 | ;(function ($) {
89 | /* Initialize function */
90 | $.fn.tablemanager = function (options = null) {
91 | /**
92 | Get common variables, parts of tables and others utilities
93 | **/
94 | var Table = $(this),
95 | Heads = $(this).find('thead th'),
96 | tbody = $(this).find('tbody'),
97 | rows = $(this).find('tbody tr'),
98 | rlen = rows.length,
99 | arr = [],
100 | cells,
101 | clen
102 |
103 | /**
104 | Options default values
105 | **/
106 | var firstSort = [[0, 0]],
107 | dateColumn = [],
108 | dateFormat = [],
109 | disableFilterBy = []
110 |
111 | /**
112 | Debug value true or false
113 | **/
114 | var debug = false
115 | var debug = options !== null && options.debug == true ? true : false
116 |
117 | /**
118 | Set pagination true or false
119 | **/
120 | var pagination = false
121 | pagination = options !== null && options.pagination == true ? true : false
122 | // default pagination variables
123 | var currentPage = 0
124 | var numPerPage =
125 | pagination !== true && showrows_option !== true ? rows.length : 5
126 | var numOfPages =
127 | options.numOfPages !== undefined && options.numOfPages > 0
128 | ? options.numOfPages
129 | : 5
130 |
131 | /**
132 | Set default show rows list or set if option is set
133 | **/
134 | var showrows = [5, 10, 50]
135 | showrows =
136 | options !== null &&
137 | options.showrows != '' &&
138 | typeof options.showrows !== undefined &&
139 | options.showrows !== undefined
140 | ? options.showrows
141 | : showrows
142 |
143 | /**
144 | Default labels translations
145 | **/
146 | var voc_filter_by = 'Filter by',
147 | voc_type_here_filter = 'Type here to filter...',
148 | voc_show_rows = 'Show rows'
149 |
150 | /**
151 | Available options:
152 | **/
153 | var availableOptions = new Array()
154 | availableOptions = [
155 | 'debug',
156 | 'firstSort',
157 | 'disable',
158 | 'appendFilterby',
159 | 'dateFormat',
160 | 'pagination',
161 | 'showrows',
162 | 'vocabulary',
163 | 'disableFilterBy',
164 | 'numOfPages',
165 | ]
166 |
167 | // debug
168 | // make array form options object
169 | arrayOptions = $.map(options, function (value, index) {
170 | return [index]
171 | })
172 | for (i = 0; i < arrayOptions.length; i++) {
173 | // check if options are in available options array
174 | if (availableOptions.indexOf(arrayOptions[i]) === -1) {
175 | if (debug) {
176 | cLog('Error! ' + arrayOptions[i] + ' is unavailable option.')
177 | }
178 | }
179 | }
180 |
181 | /**
182 | Get options if set
183 | **/
184 | if (options !== null) {
185 | /**
186 | Check options vocabulary
187 | **/
188 | if (
189 | options.vocabulary != '' &&
190 | typeof options.vocabulary !== undefined &&
191 | options.vocabulary !== undefined
192 | ) {
193 | // Check every single label
194 |
195 | voc_filter_by =
196 | options.vocabulary.voc_filter_by != '' &&
197 | options.vocabulary.voc_filter_by !== undefined
198 | ? options.vocabulary.voc_filter_by
199 | : voc_filter_by
200 |
201 | voc_type_here_filter =
202 | options.vocabulary.voc_type_here_filter != '' &&
203 | options.vocabulary.voc_type_here_filter !== undefined
204 | ? options.vocabulary.voc_type_here_filter
205 | : voc_type_here_filter
206 |
207 | voc_show_rows =
208 | options.vocabulary.voc_show_rows != '' &&
209 | options.vocabulary.voc_show_rows !== undefined
210 | ? options.vocabulary.voc_show_rows
211 | : voc_show_rows
212 | }
213 |
214 | /**
215 | Option disable
216 | **/
217 | if (
218 | options.disable != '' &&
219 | typeof options.disable !== undefined &&
220 | options.disable !== undefined
221 | ) {
222 | for (var i = 0; i < options.disable.length; i++) {
223 | // check if should be disabled last column
224 | col =
225 | options.disable[i] == -1 || options.disable[i] == 'last'
226 | ? Heads.length
227 | : options.disable[i] == 'first'
228 | ? 1
229 | : options.disable[i]
230 | Heads.eq(col - 1)
231 | .addClass('disableSort')
232 | .removeClass('sortingAsc')
233 | .removeClass('sortingDesc')
234 |
235 | // debug
236 | if (isNaN(col - 1)) {
237 | if (debug) {
238 | cLog('Error! Check your "disable" option.')
239 | }
240 | }
241 | }
242 | }
243 |
244 | /**
245 | Option select number of rows to show
246 | **/
247 | var showrows_option = false
248 | if (
249 | options.showrows != '' &&
250 | typeof options.showrows !== undefined &&
251 | options.showrows !== undefined
252 | ) {
253 | showrows_option = true
254 |
255 | // div num rows
256 | var numrowsDiv =
257 | ''
260 | // append div to choose num rows to show
261 | Table.before(numrowsDiv)
262 | // get show rows options and append select to its div
263 | for (i = 0; i < showrows.length; i++) {
264 | $('select#numrows').append(
265 | $('