Skip to content

Commit 2c0769d

Browse files
committed
Better factory + fix missing element's parent
1 parent 0d09a36 commit 2c0769d

2 files changed

Lines changed: 60 additions & 11 deletions

File tree

test/__mocks__/@wdio/globals.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,22 @@ const getElementMethods = () => ({
2828
} }, 'getSize') as unknown as WebdriverIO.Element['getSize'],
2929
} satisfies Partial<WebdriverIO.Element>)
3030

31-
const elementFactory = (_selector: string, index?: number): WebdriverIO.Element => {
31+
export const elementFactory = (_selector: string, index?: number, parent: WebdriverIO.Browser | WebdriverIO.Element = browser): WebdriverIO.Element => {
3232
const partialElement = {
3333
selector: _selector,
3434
...getElementMethods(),
3535
index,
3636
$,
37-
$$
37+
$$,
38+
parent
3839
} satisfies Partial<WebdriverIO.Element>
3940

4041
const element = partialElement as unknown as WebdriverIO.Element
4142
element.getElement = vi.fn().mockResolvedValue(element)
4243
return element
4344
}
4445

45-
function $(_selector: string) {
46+
const $ = vi.fn((_selector: string) => {
4647
const element = elementFactory(_selector)
4748

4849
// Wdio framework does return a Promise-wrapped element, so we need to mimic this behavior
@@ -59,25 +60,46 @@ function $(_selector: string) {
5960
}
6061
})
6162
return runtimeChainableElement
62-
}
63+
})
6364

64-
function $$(selector: string) {
65+
const $$ = vi.fn((selector: string) => {
6566
const length = (this as any)?._length || 2
67+
return chainableElementArrayFactory(selector, length)
68+
})
69+
70+
export function elementArrayFactory(selector: string, length?: number): WebdriverIO.ElementArray {
6671
const elements: WebdriverIO.Element[] = Array(length).fill(null).map((_, index) => elementFactory(selector, index))
6772

6873
const elementArray = elements as unknown as WebdriverIO.ElementArray
6974

7075
elementArray.foundWith = '$$'
7176
elementArray.props = []
7277
elementArray.selector = selector
73-
elementArray.getElements = async () => elementArray
78+
elementArray.getElements = vi.fn().mockResolvedValue(elementArray)
7479
elementArray.filter = async <T>(fn: (element: WebdriverIO.Element, index: number, array: T[]) => boolean | Promise<boolean>) => {
7580
const results = await Promise.all(elements.map((el, i) => fn(el, i, elements as unknown as T[])))
7681
return Array.prototype.filter.call(elements, (_, i) => results[i])
7782
}
78-
7983
elementArray.parent = browser
8084

85+
// TODO Verify if we need to implement other array methods
86+
// [Symbol.iterator]: array[Symbol.iterator].bind(array)
87+
// filter: vi.fn().mockReturnThis(),
88+
// map: vi.fn().mockReturnThis(),
89+
// find: vi.fn().mockReturnThis(),
90+
// forEach: vi.fn(),
91+
// some: vi.fn(),
92+
// every: vi.fn(),
93+
// slice: vi.fn().mockReturnThis(),
94+
// toArray: vi.fn().mockReturnThis(),
95+
// getElements: vi.fn().mockResolvedValue(array)
96+
97+
return elementArray
98+
}
99+
100+
export function chainableElementArrayFactory(selector: string, length: number) {
101+
const elementArray = elementArrayFactory(selector, length)
102+
81103
// Wdio framework does return a Promise-wrapped element, so we need to mimic this behavior
82104
const chainablePromiseArray = Promise.resolve(elementArray) as unknown as ChainablePromiseArray
83105

test/globals_mock.test.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ describe('globals mock', () => {
1010

1111
// It behaves like a promise
1212
expect(el).toHaveProperty('then')
13-
// @ts-expect-error
14-
expect(typeof el.then).toBe('function')
13+
expect(el).toBeInstanceOf(Promise)
1514
})
1615

1716
it('should resolve to an element', async () => {
@@ -22,6 +21,13 @@ describe('globals mock', () => {
2221
expect(el.getElement).toBeDefined()
2322
})
2423

24+
it('should resolve to an element on getElement', async () => {
25+
const el = await $('foo')
26+
const resolvedEl = await el.getElement()
27+
28+
expect(resolvedEl).toBe(el)
29+
})
30+
2531
it('should allow calling getElement on the chainable promise', async () => {
2632
const chainable = $('foo')
2733

@@ -46,14 +52,14 @@ describe('globals mock', () => {
4652
expect(result).toBe(true)
4753
})
4854

49-
it('should allow chaining simple methods', async () => {
55+
it('should allow chaining simple methods with await', async () => {
5056
const text = await $('foo').getText()
5157

5258
expect(text).toBe(' Valid Text ')
5359
})
5460
})
5561

56-
describe('$$', () => {
62+
describe($$, () => {
5763
it('should return a ChainablePromiseArray', async () => {
5864
const els = $$('foo')
5965
expect(els).toHaveProperty('then')
@@ -68,6 +74,12 @@ describe('globals mock', () => {
6874
expect(els.selector).toBe('foo')
6975
})
7076

77+
it('should returns ElementArray on getElements', async () => {
78+
const els = await $$('foo')
79+
80+
expect(await els.getElements()).toEqual(els)
81+
})
82+
7183
it('should allow calling getElements on the chainable promise', async () => {
7284
const chainable = $$('foo')
7385
// 'getElements' should not be present in the chainable object if checked via `in`
@@ -80,9 +92,24 @@ describe('globals mock', () => {
8092

8193
it('should allow iterating if awaited', async () => {
8294
const els = await $$('foo')
95+
8396
// map is available on the resolved array
8497
const selectors = els.map(el => el.selector)
8598
expect(selectors).toEqual(['foo', 'foo'])
8699
})
100+
101+
it('should allow calling methods like isEnabled on elements of chainable promise', async () => {
102+
const check = $$('foo')[0].isEnabled()
103+
expect(check).toBeInstanceOf(Promise)
104+
105+
const result = await check
106+
expect(result).toBe(true)
107+
})
108+
109+
it('should allow chaining simple methods with await', async () => {
110+
const text = await $$('foo')[0].getText()
111+
112+
expect(text).toBe(' Valid Text ')
113+
})
87114
})
88115
})

0 commit comments

Comments
 (0)