-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Semantic matching basics #4788 #4793
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
base: master
Are you sure you want to change the base?
Changes from all commits
becee3f
f85f86a
d2a8dff
368b454
bb366fc
df24ee9
e590858
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,9 +1,16 @@ | ||||||
| const DetoxRuntimeError = require('../../errors/DetoxRuntimeError'); | ||||||
| const invoke = require('../../invoke'); | ||||||
| const semanticTypes = require('../../matchers/semanticTypes'); | ||||||
| const { isRegExp } = require('../../utils/isRegExp'); | ||||||
| const { NativeMatcher } = require('../core/NativeMatcher'); | ||||||
| const DetoxMatcherApi = require('../espressoapi/DetoxMatcher'); | ||||||
|
|
||||||
| const createClassMatcher = (className) => | ||||||
| new NativeMatcher(invoke.callDirectly(DetoxMatcherApi.matcherForClass(className))); | ||||||
|
|
||||||
| const combineWithOr = (matchers) => | ||||||
| matchers.reduce((acc, matcher) => acc?.or(matcher) ?? matcher, null); | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Since you will have at least 1 element in matchers, better use this shorthand for |
||||||
|
|
||||||
| class LabelMatcher extends NativeMatcher { | ||||||
| constructor(value) { | ||||||
| super(); | ||||||
|
|
@@ -29,9 +36,29 @@ class IdMatcher extends NativeMatcher { | |||||
| } | ||||||
|
|
||||||
| class TypeMatcher extends NativeMatcher { | ||||||
| constructor(value) { | ||||||
| constructor(typeOrSemanticType) { | ||||||
| super(); | ||||||
| this._call = invoke.callDirectly(DetoxMatcherApi.matcherForClass(value)); | ||||||
| if (semanticTypes.includes(typeOrSemanticType)) { | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would advise also to leave some metadata like: So that this property travels as JSON to Client (Web socket). 🙏 |
||||||
| const classNames = semanticTypes.getClasses(typeOrSemanticType, 'android'); | ||||||
|
|
||||||
| const matchers = classNames.map(item => { | ||||||
| if (typeof item === 'string') return createClassMatcher(item); | ||||||
| if (!item.className || !item.excludes) return createClassMatcher(item); | ||||||
|
|
||||||
| const includeMatcher = createClassMatcher(item.className); | ||||||
| const excludeCombined = combineWithOr(item.excludes.map(createClassMatcher)); | ||||||
|
|
||||||
| return includeMatcher.and(excludeCombined.not); | ||||||
| }); | ||||||
|
|
||||||
| const combinedMatcher = combineWithOr(matchers); | ||||||
| if (!combinedMatcher) { | ||||||
| throw new DetoxRuntimeError(`No class names found for semantic type: ${typeOrSemanticType}`); | ||||||
| } | ||||||
| this._call = combinedMatcher._call; | ||||||
| } else { | ||||||
| this._call = invoke.callDirectly(DetoxMatcherApi.matcherForClass(typeOrSemanticType)); | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| // @ts-nocheck | ||
| // Mock the semanticTypes module before importing anything that depends on it | ||
| jest.mock('../../matchers/semanticTypes', () => ({ | ||
| getTypes: jest.fn(), | ||
| getClasses: jest.fn(), | ||
| includes: jest.fn() | ||
| })); | ||
|
|
||
|
|
||
| const semanticTypes = require('../../matchers/semanticTypes'); | ||
|
|
||
| const { TypeMatcher } = require('./native'); | ||
|
|
||
| describe('Native Matchers', () => { | ||
| describe('TypeMatcher', () => { | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| }); | ||
|
|
||
| it('should handle regular class names', () => { | ||
| semanticTypes.includes.mockReturnValue(false); | ||
|
|
||
| expect(() => { | ||
| new TypeMatcher('com.example.CustomView'); | ||
| }).not.toThrow(); | ||
| }); | ||
|
|
||
| it('should handle semantic types automatically', () => { | ||
| semanticTypes.includes.mockReturnValue(true); | ||
| semanticTypes.getClasses.mockReturnValue([ | ||
| 'android.widget.ImageView', | ||
| 'com.facebook.react.views.image.ReactImageView' | ||
| ]); | ||
|
|
||
| expect(() => { | ||
| new TypeMatcher('image'); | ||
| }).not.toThrow(); | ||
| }); | ||
|
|
||
| it('should handle exclusion objects for semantic types', () => { | ||
| semanticTypes.includes.mockReturnValue(true); | ||
| semanticTypes.getClasses.mockReturnValue([ | ||
| { | ||
| className: 'android.widget.ProgressBar', | ||
| excludes: ['android.widget.AbsSeekBar'] | ||
| }, | ||
| { | ||
| className: 'androidx.core.widget.ContentLoadingProgressBar', | ||
| excludes: ['android.widget.AbsSeekBar'] | ||
| } | ||
| ]); | ||
|
|
||
| expect(() => { | ||
| new TypeMatcher('activity-indicator'); | ||
| }).not.toThrow(); | ||
| }); | ||
|
|
||
| it('should handle mixed string and exclusion objects', () => { | ||
| semanticTypes.includes.mockReturnValue(true); | ||
| semanticTypes.getClasses.mockReturnValue([ | ||
| { | ||
| className: 'android.widget.ProgressBar', | ||
| excludes: ['android.widget.AbsSeekBar'] | ||
| }, | ||
| { | ||
| className: 'androidx.core.widget.ContentLoadingProgressBar', | ||
| excludes: ['android.widget.AbsSeekBar'] | ||
| } | ||
| ]); | ||
|
|
||
| expect(() => { | ||
| new TypeMatcher('progress'); | ||
| }).not.toThrow(); | ||
| }); | ||
|
|
||
| it('should handle regular class names when not semantic types', () => { | ||
| semanticTypes.includes.mockReturnValue(false); | ||
|
|
||
| expect(() => { | ||
| new TypeMatcher('android.widget.ImageView'); | ||
| }).not.toThrow(); | ||
| }); | ||
| }); | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.