feat(toBeChecked): allow passing checked: false (#10665)
This commit is contained in:
		
							parent
							
								
									2ac9c08d0c
								
							
						
					
					
						commit
						31e0a63fcd
					
				|  | @ -280,8 +280,11 @@ locator = page.locator(".subscribe") | |||
| expect(locator).to_be_checked() | ||||
| ``` | ||||
| 
 | ||||
| ### option: LocatorAssertions.toBeChecked.timeout = %%-assertions-timeout-%% | ||||
| ### option: LocatorAssertions.toBeChecked.checked | ||||
| * langs: js | ||||
| - `checked` <[boolean]> | ||||
| 
 | ||||
| ### option: LocatorAssertions.toBeChecked.timeout = %%-assertions-timeout-%% | ||||
| 
 | ||||
| 
 | ||||
| ## method: LocatorAssertions.toBeDisabled | ||||
|  |  | |||
|  | @ -234,7 +234,7 @@ export class Locator implements api.Locator { | |||
|     await this._frame._channel.waitForSelector({ selector: this._selector, strict: true, omitReturnValue: true, ...options }); | ||||
|   } | ||||
| 
 | ||||
|   async _expect(expression: string, options: FrameExpectOptions): Promise<{ matches: boolean, received?: any, log?: string[] }> { | ||||
|   async _expect(expression: string, options: Omit<FrameExpectOptions, 'expectedValue'> & { expectedValue?: any }): Promise<{ matches: boolean, received?: any, log?: string[] }> { | ||||
|     const params: channels.FrameExpectParams = { selector: this._selector, expression, ...options, isNot: !!options.isNot }; | ||||
|     if (options.expectedValue) | ||||
|       params.expectedValue = serializeArgument(options.expectedValue); | ||||
|  |  | |||
|  | @ -51,7 +51,7 @@ export type InjectedScriptPoll<T> = { | |||
|   cancel: () => void, | ||||
| }; | ||||
| 
 | ||||
| export type ElementStateWithoutStable = 'visible' | 'hidden' | 'enabled' | 'disabled' | 'editable' | 'checked'; | ||||
| export type ElementStateWithoutStable = 'visible' | 'hidden' | 'enabled' | 'disabled' | 'editable' | 'checked' | 'unchecked'; | ||||
| export type ElementState = ElementStateWithoutStable | 'stable'; | ||||
| 
 | ||||
| export interface SelectorEngineV2 { | ||||
|  | @ -502,14 +502,17 @@ export class InjectedScript { | |||
|     if (state === 'editable') | ||||
|       return !disabled && editable; | ||||
| 
 | ||||
|     if (state === 'checked') { | ||||
|       if (['checkbox', 'radio'].includes(element.getAttribute('role') || '')) | ||||
|         return element.getAttribute('aria-checked') === 'true'; | ||||
|     if (state === 'checked' || state === 'unchecked') { | ||||
|       if (['checkbox', 'radio'].includes(element.getAttribute('role') || '')) { | ||||
|         const result = element.getAttribute('aria-checked') === 'true'; | ||||
|         return state === 'checked' ? result : !result; | ||||
|       } | ||||
|       if (element.nodeName !== 'INPUT') | ||||
|         throw this.createStacklessError('Not a checkbox or radio button'); | ||||
|       if (!['radio', 'checkbox'].includes((element as HTMLInputElement).type.toLowerCase())) | ||||
|         throw this.createStacklessError('Not a checkbox or radio button'); | ||||
|       return (element as HTMLInputElement).checked; | ||||
|       const result = (element as HTMLInputElement).checked; | ||||
|       return state === 'checked' ? result : !result; | ||||
|     } | ||||
|     throw this.createStacklessError(`Unexpected element state "${state}"`); | ||||
|   } | ||||
|  | @ -899,6 +902,8 @@ export class InjectedScript { | |||
|       let elementState: boolean | 'error:notconnected' | 'error:notcheckbox' | undefined; | ||||
|       if (expression === 'to.be.checked') { | ||||
|         elementState = progress.injectedScript.elementState(element, 'checked'); | ||||
|       } else if (expression === 'to.be.unchecked') { | ||||
|         elementState = progress.injectedScript.elementState(element, 'unchecked'); | ||||
|       } else if (expression === 'to.be.disabled') { | ||||
|         elementState = progress.injectedScript.elementState(element, 'disabled'); | ||||
|       } else if (expression === 'to.be.editable') { | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ import { toEqual } from './toEqual'; | |||
| import { callLogText, toExpectedTextValues, toMatchText } from './toMatchText'; | ||||
| 
 | ||||
| interface LocatorEx extends Locator { | ||||
|   _expect(expression: string, options: FrameExpectOptions): Promise<{ matches: boolean, received?: any, log?: string[] }>; | ||||
|   _expect(expression: string, options: Omit<FrameExpectOptions, 'expectedValue'> & { expectedValue?: any }): Promise<{ matches: boolean, received?: any, log?: string[] }>; | ||||
| } | ||||
| 
 | ||||
| interface APIResponseEx extends APIResponse { | ||||
|  | @ -34,10 +34,11 @@ interface APIResponseEx extends APIResponse { | |||
| export function toBeChecked( | ||||
|   this: ReturnType<Expect['getState']>, | ||||
|   locator: LocatorEx, | ||||
|   options?: { timeout?: number }, | ||||
|   options?: { checked?: boolean, timeout?: number }, | ||||
| ) { | ||||
|   return toBeTruthy.call(this, 'toBeChecked', locator, 'Locator', async (isNot, timeout) => { | ||||
|     return await locator._expect('to.be.checked', { isNot, timeout }); | ||||
|     const checked = !options || options.checked === undefined || options.checked === true; | ||||
|     return await locator._expect(checked ? 'to.be.checked' : 'to.be.unchecked', { isNot, timeout }); | ||||
|   }, options); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,9 +1,17 @@ | |||
| /** | ||||
|  * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. | ||||
|  * Modifications copyright (c) Microsoft Corporation. | ||||
|  * Copyright (c) Microsoft Corporation. | ||||
|  * | ||||
|  * This source code is licensed under the MIT license found in the | ||||
|  * LICENSE file in the root directory of this source tree. | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| import type * as expect from 'expect'; | ||||
|  | @ -69,9 +77,9 @@ declare global { | |||
|       }): R; | ||||
| 
 | ||||
|       /** | ||||
|        * Asserts input is checked. | ||||
|        * Asserts input is checked (or unchecked if { checked: false } is passed). | ||||
|        */ | ||||
|       toBeChecked(options?: { timeout?: number }): Promise<R>; | ||||
|       toBeChecked(options?: { checked?: boolean, timeout?: number }): Promise<R>; | ||||
| 
 | ||||
|       /** | ||||
|        * Asserts input is disabled. | ||||
|  |  | |||
|  | @ -27,6 +27,18 @@ test('should support toBeChecked', async ({ runInlineTest }) => { | |||
|         await expect(locator).toBeChecked(); | ||||
|       }); | ||||
| 
 | ||||
|       test('pass 2', async ({ page }) => { | ||||
|         await page.setContent('<input type=checkbox checked></input>'); | ||||
|         const locator = page.locator('input'); | ||||
|         await expect(locator).toBeChecked({ checked: true }); | ||||
|       }); | ||||
| 
 | ||||
|       test('pass 3', async ({ page }) => { | ||||
|         await page.setContent('<input type=checkbox checked></input>'); | ||||
|         const locator = page.locator('input'); | ||||
|         await expect(locator).not.toBeChecked({ checked: false }); | ||||
|       }); | ||||
| 
 | ||||
|       test('fail', async ({ page }) => { | ||||
|         await page.setContent('<input type=checkbox></input>'); | ||||
|         const locator = page.locator('input'); | ||||
|  | @ -37,7 +49,7 @@ test('should support toBeChecked', async ({ runInlineTest }) => { | |||
|   const output = stripAscii(result.output); | ||||
|   expect(output).toContain('Error: expect(received).toBeChecked()'); | ||||
|   expect(output).toContain('expect(locator).toBeChecked'); | ||||
|   expect(result.passed).toBe(1); | ||||
|   expect(result.passed).toBe(3); | ||||
|   expect(result.failed).toBe(1); | ||||
|   expect(result.exitCode).toBe(1); | ||||
| }); | ||||
|  | @ -53,22 +65,34 @@ test('should support toBeChecked w/ not', async ({ runInlineTest }) => { | |||
|         await expect(locator).not.toBeChecked(); | ||||
|       }); | ||||
| 
 | ||||
|       test('pass 2', async ({ page }) => { | ||||
|         await page.setContent('<input type=checkbox></input>'); | ||||
|         const locator = page.locator('input'); | ||||
|         await expect(locator).toBeChecked({ checked: false }); | ||||
|       }); | ||||
| 
 | ||||
|       test('fail not', async ({ page }) => { | ||||
|         await page.setContent('<input type=checkbox checked></input>'); | ||||
|         const locator = page.locator('input'); | ||||
|         await expect(locator).not.toBeChecked({ timeout: 1000 }); | ||||
|         await expect(locator).not.toBeChecked({ timeout: 500 }); | ||||
|       }); | ||||
| 
 | ||||
|       test('fail 2', async ({ page }) => { | ||||
|         await page.setContent('<input type=checkbox checked></input>'); | ||||
|         const locator = page.locator('input'); | ||||
|         await expect(locator).toBeChecked({ checked: false, timeout: 500 }); | ||||
|       }); | ||||
| 
 | ||||
|       test('fail missing', async ({ page }) => { | ||||
|         await page.setContent('<div>no inputs here</div>'); | ||||
|         const locator2 = page.locator('input2'); | ||||
|         await expect(locator2).not.toBeChecked({ timeout: 1000 }); | ||||
|         await expect(locator2).not.toBeChecked({ timeout: 500 }); | ||||
|       }); | ||||
|       `,
 | ||||
|   }, { workers: 1 }); | ||||
|   const output = stripAscii(result.output); | ||||
|   expect(result.passed).toBe(1); | ||||
|   expect(result.failed).toBe(2); | ||||
|   expect(result.passed).toBe(2); | ||||
|   expect(result.failed).toBe(3); | ||||
|   expect(result.exitCode).toBe(1); | ||||
|   // fail not
 | ||||
|   expect(output).toContain('Error: expect(received).not.toBeChecked()'); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue