Add coexistence checks to all enqueue methods to prevent loading both React and Grid.js assets simultaneously. Changes: - ReactAdmin.php: Only enqueue React assets when ?react=1 - Init.php: Skip Grid.js when React active on admin pages - Form.php, Coupon.php, Access.php: Restore classic assets when ?react=0 - Customer.php, Product.php, License.php: Add coexistence checks Now the toggle between Classic and React versions works correctly. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
151 lines
6.5 KiB
JavaScript
151 lines
6.5 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.getMatcherCall = void 0;
|
|
const ast_1 = require("../utils/ast");
|
|
const parseExpectCall_1 = require("../utils/parseExpectCall");
|
|
const methods = {
|
|
getAttribute: {
|
|
matcher: 'toHaveAttribute',
|
|
type: 'string',
|
|
},
|
|
innerText: { matcher: 'toHaveText', type: 'string' },
|
|
inputValue: { matcher: 'toHaveValue', type: 'string' },
|
|
isChecked: { matcher: 'toBeChecked', type: 'boolean' },
|
|
isDisabled: {
|
|
inverse: 'toBeEnabled',
|
|
matcher: 'toBeDisabled',
|
|
type: 'boolean',
|
|
},
|
|
isEditable: { matcher: 'toBeEditable', type: 'boolean' },
|
|
isEnabled: {
|
|
inverse: 'toBeDisabled',
|
|
matcher: 'toBeEnabled',
|
|
type: 'boolean',
|
|
},
|
|
isHidden: {
|
|
inverse: 'toBeVisible',
|
|
matcher: 'toBeHidden',
|
|
type: 'boolean',
|
|
},
|
|
isVisible: {
|
|
inverse: 'toBeHidden',
|
|
matcher: 'toBeVisible',
|
|
type: 'boolean',
|
|
},
|
|
textContent: { matcher: 'toHaveText', type: 'string' },
|
|
};
|
|
const supportedMatchers = new Set([
|
|
'toBe',
|
|
'toEqual',
|
|
'toBeTruthy',
|
|
'toBeFalsy',
|
|
]);
|
|
function getMatcherCall(node) {
|
|
const grandparent = node.parent?.parent;
|
|
return grandparent.type === 'CallExpression' ? grandparent : undefined;
|
|
}
|
|
exports.getMatcherCall = getMatcherCall;
|
|
exports.default = {
|
|
create(context) {
|
|
return {
|
|
CallExpression(node) {
|
|
const expectCall = (0, parseExpectCall_1.parseExpectCall)(node);
|
|
if (!expectCall)
|
|
return;
|
|
const [arg] = node.arguments;
|
|
if (arg.type !== 'AwaitExpression' ||
|
|
arg.argument.type !== 'CallExpression' ||
|
|
arg.argument.callee.type !== 'MemberExpression') {
|
|
return;
|
|
}
|
|
// Matcher must be supported
|
|
if (!supportedMatchers.has(expectCall.matcherName))
|
|
return;
|
|
// Playwright method must be supported
|
|
const method = (0, ast_1.getStringValue)(arg.argument.callee.property);
|
|
const methodConfig = methods[method];
|
|
if (!methodConfig)
|
|
return;
|
|
// Change the matcher
|
|
const { args, matcher } = expectCall;
|
|
const notModifier = expectCall.modifiers.find((mod) => (0, ast_1.getStringValue)(mod) === 'not');
|
|
const isFalsy = methodConfig.type === 'boolean' &&
|
|
((!!args.length && (0, ast_1.isBooleanLiteral)(args[0], false)) ||
|
|
expectCall.matcherName === 'toBeFalsy');
|
|
const isInverse = methodConfig.inverse
|
|
? notModifier || isFalsy
|
|
: notModifier && isFalsy;
|
|
// Replace the old matcher with the new matcher. The inverse
|
|
// matcher should only be used if the old statement was not a
|
|
// double negation.
|
|
const newMatcher = (+!!notModifier ^ +isFalsy && methodConfig.inverse) ||
|
|
methodConfig.matcher;
|
|
const { callee } = arg.argument;
|
|
context.report({
|
|
data: {
|
|
matcher: newMatcher,
|
|
method,
|
|
},
|
|
fix: (fixer) => {
|
|
const methodArgs = arg.argument.type === 'CallExpression'
|
|
? arg.argument.arguments
|
|
: [];
|
|
const methodEnd = methodArgs.length
|
|
? methodArgs[methodArgs.length - 1].range[1] + 1
|
|
: callee.property.range[1] + 2;
|
|
const fixes = [
|
|
// Add await to the expect call
|
|
fixer.insertTextBefore(node, 'await '),
|
|
// Remove the await keyword
|
|
fixer.replaceTextRange([arg.range[0], arg.argument.range[0]], ''),
|
|
// Remove the old Playwright method and any arguments
|
|
fixer.replaceTextRange([callee.property.range[0] - 1, methodEnd], ''),
|
|
];
|
|
// Remove not from matcher chain if no longer needed
|
|
if (isInverse && notModifier) {
|
|
const notRange = notModifier.range;
|
|
fixes.push(fixer.removeRange([notRange[0], notRange[1] + 1]));
|
|
}
|
|
// Add not to the matcher chain if no inverse matcher exists
|
|
if (!methodConfig.inverse && !notModifier && isFalsy) {
|
|
fixes.push(fixer.insertTextBefore(matcher, 'not.'));
|
|
}
|
|
fixes.push(fixer.replaceText(matcher, newMatcher));
|
|
// Remove boolean argument if it exists
|
|
const [matcherArg] = args ?? [];
|
|
if (matcherArg && (0, ast_1.isBooleanLiteral)(matcherArg)) {
|
|
fixes.push(fixer.remove(matcherArg));
|
|
}
|
|
// Add the new matcher arguments if needed
|
|
const hasOtherArgs = !!methodArgs.filter((arg) => !(0, ast_1.isBooleanLiteral)(arg)).length;
|
|
if (methodArgs) {
|
|
const range = matcher.range;
|
|
const stringArgs = methodArgs
|
|
.map((arg) => (0, ast_1.getRawValue)(arg))
|
|
.concat(hasOtherArgs ? '' : [])
|
|
.join(', ');
|
|
fixes.push(fixer.insertTextAfterRange([range[0], range[1] + 1], stringArgs));
|
|
}
|
|
return fixes;
|
|
},
|
|
messageId: 'useWebFirstAssertion',
|
|
node,
|
|
});
|
|
},
|
|
};
|
|
},
|
|
meta: {
|
|
docs: {
|
|
category: 'Best Practices',
|
|
description: 'Prefer web first assertions',
|
|
recommended: true,
|
|
url: 'https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-web-first-assertions.md',
|
|
},
|
|
fixable: 'code',
|
|
messages: {
|
|
useWebFirstAssertion: 'Replace {{method}}() with {{matcher}}().',
|
|
},
|
|
type: 'suggestion',
|
|
},
|
|
};
|