diff --git a/bin/react_18_upgrade.dart b/bin/react_18_upgrade.dart new file mode 100644 index 00000000..7485030c --- /dev/null +++ b/bin/react_18_upgrade.dart @@ -0,0 +1,15 @@ +// Copyright 2025 Workiva Inc. +// +// 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. + +export 'package:over_react_codemod/src/executables/react_18_upgrade.dart'; diff --git a/lib/src/executables/react_18_upgrade.dart b/lib/src/executables/react_18_upgrade.dart new file mode 100644 index 00000000..dd882f27 --- /dev/null +++ b/lib/src/executables/react_18_upgrade.dart @@ -0,0 +1,91 @@ +// Copyright 2025 Workiva Inc. +// +// 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 'dart:io'; + +import 'package:args/args.dart'; +import 'package:codemod/codemod.dart'; +import 'package:over_react_codemod/src/rmui_bundle_update_suggestors/dart_script_updater.dart'; +import 'package:over_react_codemod/src/rmui_bundle_update_suggestors/html_script_updater.dart'; +import 'package:over_react_codemod/src/util.dart'; + +const _changesRequiredOutput = """ + To update your code, run the following commands in your repository: + dart pub global activate over_react_codemod + dart pub global run over_react_codemod:react_18_upgrade +"""; + +/// Updates React JS paths in HTML and Dart files from the React 17 versions to the React 18 versions. +void main(List args) async { + final parser = ArgParser.allowAnything(); + + final parsedArgs = parser.parse(args); + + // Work around allowAnything not allowing you to pass flags. + if (parsedArgs.arguments.contains('--help')) { + // Print command description; flags and other output will get printed via runInteractiveCodemodSequence. + print( + 'Updates React JS paths in HTML and Dart files from the React 17 versions to the React 18 versions.\n'); + } + + exitCode = await runInteractiveCodemodSequence( + allHtmlPathsIncludingTemplates(), + [ + // Update react.js bundle files to React 18 versions in html files + ...react17to18ReactJsScriptNames.keys.map((key) => HtmlScriptUpdater( + key, react17to18ReactJsScriptNames[key]!, + updateAttributes: false)), + // Remove React 17 react_dom bundle files in html files + ...react17ReactDomJsOnlyScriptNames + .map((name) => HtmlScriptUpdater.remove(name)), + ], + defaultYes: true, + args: parsedArgs.rest, + additionalHelpOutput: parser.usage, + changesRequiredOutput: _changesRequiredOutput, + ); + + if (exitCode != 0) return; + + exitCode = await runInteractiveCodemodSequence( + allDartPathsExceptHidden(), + [ + // Update react.js bundle files to React 18 versions in Dart files + ...react17to18ReactJsScriptNames.keys.map((key) => DartScriptUpdater( + key, react17to18ReactJsScriptNames[key]!, + updateAttributes: false)), + // Remove React 17 react_dom bundle files in Dart files + ...react17ReactDomJsOnlyScriptNames + .map((name) => DartScriptUpdater.remove(name)), + ], + defaultYes: true, + args: parsedArgs.rest, + additionalHelpOutput: parser.usage, + changesRequiredOutput: _changesRequiredOutput, + ); +} + +const reactPath = 'packages/react/'; + +const react17to18ReactJsScriptNames = { + '${reactPath}react.js': '${reactPath}js/react.dev.js', + '${reactPath}react_with_addons.js': '${reactPath}js/react.dev.js', + '${reactPath}react_prod.js': '${reactPath}js/react.min.js', + '${reactPath}react_with_react_dom_prod.js': '${reactPath}js/react.min.js', +}; + +const react17ReactDomJsOnlyScriptNames = [ + '${reactPath}react_dom.js', + '${reactPath}react_dom_prod.js', +]; diff --git a/lib/src/rmui_bundle_update_suggestors/constants.dart b/lib/src/rmui_bundle_update_suggestors/constants.dart index b8fe71e0..33e532e5 100644 --- a/lib/src/rmui_bundle_update_suggestors/constants.dart +++ b/lib/src/rmui_bundle_update_suggestors/constants.dart @@ -53,7 +53,9 @@ class Link { /// A pattern for finding a link tag with a matching path. RegExp get pattern => RegExp( - r']*href="(?[^"]*)' + pathSubpattern + r'"[^>]*>'); + r'(?[^\S\r\n]*)]*href="(?[^"]*)' + + pathSubpattern + + r'"[^>]*>(?\n?)'); @override String toString() => diff --git a/lib/src/rmui_bundle_update_suggestors/dart_script_updater.dart b/lib/src/rmui_bundle_update_suggestors/dart_script_updater.dart index b0929c28..3588a8f9 100644 --- a/lib/src/rmui_bundle_update_suggestors/dart_script_updater.dart +++ b/lib/src/rmui_bundle_update_suggestors/dart_script_updater.dart @@ -28,7 +28,20 @@ class DartScriptUpdater extends RecursiveAstVisitor final String existingScriptPath; final String newScriptPath; - DartScriptUpdater(this.existingScriptPath, this.newScriptPath); + /// Whether or not to update attributes on script/link tags (like type/crossorigin) + /// while also updating the script path. + final bool updateAttributes; + final bool removeTag; + + DartScriptUpdater(this.existingScriptPath, this.newScriptPath, + {this.updateAttributes = true}) + : removeTag = false; + + /// Use this constructor to remove the whole tag instead of updating it. + DartScriptUpdater.remove(this.existingScriptPath) + : removeTag = true, + updateAttributes = false, + newScriptPath = 'will be ignored'; @override void visitSimpleStringLiteral(SimpleStringLiteral node) { @@ -39,74 +52,107 @@ class DartScriptUpdater extends RecursiveAstVisitor ...Script(pathSubpattern: existingScriptPath) .pattern .allMatches(stringValue), - ...Script(pathSubpattern: newScriptPath).pattern.allMatches(stringValue) + ...?(!removeTag + ? Script(pathSubpattern: newScriptPath) + .pattern + .allMatches(stringValue) + : null) ]; final relevantLinkTags = [ ...Link(pathSubpattern: existingScriptPath) .pattern .allMatches(stringValue), - ...Link(pathSubpattern: newScriptPath).pattern.allMatches(stringValue) + ...?(!removeTag + ? Link(pathSubpattern: newScriptPath).pattern.allMatches(stringValue) + : null) ]; // Do not update if neither the existingScriptPath nor newScriptPath are in the file. if (relevantScriptTags.isEmpty && relevantLinkTags.isEmpty) return; - // Add type="module" attribute to script tag. - for (final scriptTagMatch in relevantScriptTags) { - final scriptTag = scriptTagMatch.group(0); - if (scriptTag == null) continue; - final typeAttributes = getAttributePattern('type').allMatches(scriptTag); - if (typeAttributes.isNotEmpty) { - final attribute = typeAttributes.first; - final value = attribute.group(1); - if (value == 'module') { - continue; + if (removeTag) { + for (final tag in [...relevantScriptTags, ...relevantLinkTags]) { + final tagEnd = node.offset + tag.end; + final tagStart = node.offset + tag.start; + final possibleCommaEnd = node.literal.next.toString() == ',' ? 1 : 0; + final isTagSameAsNode = + // Check if the only difference between [tag] and [node] is the quotes around [node]. + tagStart - node.offset <= 1 && node.end - tagEnd <= 1; + + yieldPatch( + '', + // If [tag] spans the whole string literal in [node], then also include + // the quotes and comma in the removal. + isTagSameAsNode + // Remove from the end of the previous token to take any preceding newline with it, + // so that we don't leave behind an empty line. + ? node.beginToken.previous?.end ?? node.offset + : tagStart, + isTagSameAsNode ? (node.end + possibleCommaEnd) : tagEnd, + ); + } + return; + } + + if (updateAttributes) { + // Add type="module" attribute to script tag. + for (final scriptTagMatch in relevantScriptTags) { + final scriptTag = scriptTagMatch.group(0); + if (scriptTag == null) continue; + final typeAttributes = + getAttributePattern('type').allMatches(scriptTag); + if (typeAttributes.isNotEmpty) { + final attribute = typeAttributes.first; + final value = attribute.group(1); + if (value == 'module') { + continue; + } else { + // If the value of the type attribute is not "module", overwrite it. + yieldPatch( + typeModuleAttribute, + node.offset + scriptTagMatch.start + attribute.start, + node.offset + scriptTagMatch.start + attribute.end, + ); + } } else { - // If the value of the type attribute is not "module", overwrite it. + // If the type attribute does not exist, add it. + final srcAttribute = getAttributePattern('src').allMatches(scriptTag); yieldPatch( - typeModuleAttribute, - node.offset + scriptTagMatch.start + attribute.start, - node.offset + scriptTagMatch.start + attribute.end, + ' ${typeModuleAttribute}', + node.offset + scriptTagMatch.start + srcAttribute.first.end, + node.offset + scriptTagMatch.start + srcAttribute.first.end, ); } - } else { - // If the type attribute does not exist, add it. - final srcAttribute = getAttributePattern('src').allMatches(scriptTag); - yieldPatch( - ' ${typeModuleAttribute}', - node.offset + scriptTagMatch.start + srcAttribute.first.end, - node.offset + scriptTagMatch.start + srcAttribute.first.end, - ); } - } - // Add crossorigin="" attribute to link tag. - for (final linkTagMatch in relevantLinkTags) { - final linkTag = linkTagMatch.group(0); - if (linkTag == null) continue; - final crossOriginAttributes = - getAttributePattern('crossorigin').allMatches(linkTag); - if (crossOriginAttributes.isNotEmpty) { - final attribute = crossOriginAttributes.first; - final value = attribute.group(1); - if (value == '') { - continue; + // Add crossorigin="" attribute to link tag. + for (final linkTagMatch in relevantLinkTags) { + final linkTag = linkTagMatch.group(0); + if (linkTag == null) continue; + final crossOriginAttributes = + getAttributePattern('crossorigin').allMatches(linkTag); + if (crossOriginAttributes.isNotEmpty) { + final attribute = crossOriginAttributes.first; + final value = attribute.group(1); + if (value == '') { + continue; + } else { + // If the value of the crossorigin attribute is not "", overwrite it. + yieldPatch( + crossOriginAttribute, + node.offset + linkTagMatch.start + attribute.start, + node.offset + linkTagMatch.start + attribute.end, + ); + } } else { - // If the value of the crossorigin attribute is not "", overwrite it. + // If the crossorigin attribute does not exist, add it. + final hrefAttribute = getAttributePattern('href').allMatches(linkTag); yieldPatch( - crossOriginAttribute, - node.offset + linkTagMatch.start + attribute.start, - node.offset + linkTagMatch.start + attribute.end, + ' ${crossOriginAttribute}', + node.offset + linkTagMatch.start + hrefAttribute.first.end, + node.offset + linkTagMatch.start + hrefAttribute.first.end, ); } - } else { - // If the crossorigin attribute does not exist, add it. - final hrefAttribute = getAttributePattern('href').allMatches(linkTag); - yieldPatch( - ' ${crossOriginAttribute}', - node.offset + linkTagMatch.start + hrefAttribute.first.end, - node.offset + linkTagMatch.start + hrefAttribute.first.end, - ); } } diff --git a/lib/src/rmui_bundle_update_suggestors/html_script_updater.dart b/lib/src/rmui_bundle_update_suggestors/html_script_updater.dart index ea337f8b..61576227 100644 --- a/lib/src/rmui_bundle_update_suggestors/html_script_updater.dart +++ b/lib/src/rmui_bundle_update_suggestors/html_script_updater.dart @@ -24,24 +24,41 @@ class HtmlScriptUpdater { final String existingScriptPath; final String newScriptPath; - HtmlScriptUpdater(this.existingScriptPath, this.newScriptPath); + /// Whether or not to update attributes on script/link tags (like type/crossorigin) + /// while also updating the script path. + final bool updateAttributes; + final bool removeTag; + + HtmlScriptUpdater(this.existingScriptPath, this.newScriptPath, + {this.updateAttributes = true}) + : removeTag = false; + + /// Use this constructor to remove the whole tag instead of updating it. + HtmlScriptUpdater.remove(this.existingScriptPath) + : removeTag = true, + updateAttributes = false, + newScriptPath = 'will be ignored'; Stream call(FileContext context) async* { final relevantScriptTags = [ ...Script(pathSubpattern: existingScriptPath) .pattern .allMatches(context.sourceText), - ...Script(pathSubpattern: newScriptPath) - .pattern - .allMatches(context.sourceText) + ...?(!removeTag + ? Script(pathSubpattern: newScriptPath) + .pattern + .allMatches(context.sourceText) + : null) ]; final relevantLinkTags = [ ...Link(pathSubpattern: existingScriptPath) .pattern .allMatches(context.sourceText), - ...Link(pathSubpattern: newScriptPath) - .pattern - .allMatches(context.sourceText) + ...?(!removeTag + ? Link(pathSubpattern: newScriptPath) + .pattern + .allMatches(context.sourceText) + : null) ]; // Do not update if neither the existingScriptPath nor newScriptPath are in the file. @@ -49,75 +66,90 @@ class HtmlScriptUpdater { final patches = []; - // Add type="module" attribute to script tag. - for (final scriptTagMatch in relevantScriptTags) { - final scriptTag = scriptTagMatch.group(0); - if (scriptTag == null) continue; - final typeAttributes = getAttributePattern('type').allMatches(scriptTag); - if (typeAttributes.isNotEmpty) { - final attribute = typeAttributes.first; - final value = attribute.group(1); - if (value == 'module') { - continue; - } else { - // If the value of the type attribute is not "module", overwrite it. - patches.add(Patch( - typeModuleAttribute, - scriptTagMatch.start + attribute.start, - scriptTagMatch.start + attribute.end, - )); - } - } else { - // If the type attribute does not exist, add it. - final srcAttribute = getAttributePattern('src').allMatches(scriptTag); + if (removeTag) { + for (final tag in [...relevantScriptTags, ...relevantLinkTags]) { patches.add(Patch( - ' ${typeModuleAttribute}', - scriptTagMatch.start + srcAttribute.first.end, - scriptTagMatch.start + srcAttribute.first.end, + '', + tag.start, + tag.end, )); } - } + } else { + if (updateAttributes) { + // Add type="module" attribute to script tag. + for (final scriptTagMatch in relevantScriptTags) { + final scriptTag = scriptTagMatch.group(0); + if (scriptTag == null) continue; + final typeAttributes = + getAttributePattern('type').allMatches(scriptTag); + if (typeAttributes.isNotEmpty) { + final attribute = typeAttributes.first; + final value = attribute.group(1); + if (value == 'module') { + continue; + } else { + // If the value of the type attribute is not "module", overwrite it. + patches.add(Patch( + typeModuleAttribute, + scriptTagMatch.start + attribute.start, + scriptTagMatch.start + attribute.end, + )); + } + } else { + // If the type attribute does not exist, add it. + final srcAttribute = + getAttributePattern('src').allMatches(scriptTag); + patches.add(Patch( + ' ${typeModuleAttribute}', + scriptTagMatch.start + srcAttribute.first.end, + scriptTagMatch.start + srcAttribute.first.end, + )); + } + } - // Add crossorigin="" attribute to link tag. - for (final linkTagToMatch in relevantLinkTags) { - final linkTag = linkTagToMatch.group(0); - if (linkTag == null) continue; - final crossOriginAttributes = - getAttributePattern('crossorigin').allMatches(linkTag); - if (crossOriginAttributes.isNotEmpty) { - final attribute = crossOriginAttributes.first; - final value = attribute.group(1); - if (value == '') { - continue; - } else { - // If the value of the crossorigin attribute is not "", overwrite it. - patches.add(Patch( - crossOriginAttribute, - linkTagToMatch.start + attribute.start, - linkTagToMatch.start + attribute.end, - )); + // Add crossorigin="" attribute to link tag. + for (final linkTagToMatch in relevantLinkTags) { + final linkTag = linkTagToMatch.group(0); + if (linkTag == null) continue; + final crossOriginAttributes = + getAttributePattern('crossorigin').allMatches(linkTag); + if (crossOriginAttributes.isNotEmpty) { + final attribute = crossOriginAttributes.first; + final value = attribute.group(1); + if (value == '') { + continue; + } else { + // If the value of the crossorigin attribute is not "", overwrite it. + patches.add(Patch( + crossOriginAttribute, + linkTagToMatch.start + attribute.start, + linkTagToMatch.start + attribute.end, + )); + } + } else { + // If the crossorigin attribute does not exist, add it. + final hrefAttribute = + getAttributePattern('href').allMatches(linkTag); + patches.add(Patch( + ' ${crossOriginAttribute}', + linkTagToMatch.start + hrefAttribute.first.end, + linkTagToMatch.start + hrefAttribute.first.end, + )); + } } - } else { - // If the crossorigin attribute does not exist, add it. - final hrefAttribute = getAttributePattern('href').allMatches(linkTag); + } + + // Update existing path to new path. + final scriptMatches = existingScriptPath.allMatches(context.sourceText); + scriptMatches.forEach((match) async { patches.add(Patch( - ' ${crossOriginAttribute}', - linkTagToMatch.start + hrefAttribute.first.end, - linkTagToMatch.start + hrefAttribute.first.end, + newScriptPath, + match.start, + match.end, )); - } + }); } - // Update existing path to new path. - final scriptMatches = existingScriptPath.allMatches(context.sourceText); - scriptMatches.forEach((match) async { - patches.add(Patch( - newScriptPath, - match.start, - match.end, - )); - }); - yield* Stream.fromIterable(patches); } } diff --git a/lib/src/rmui_preparation_suggestors/constants.dart b/lib/src/rmui_preparation_suggestors/constants.dart index 698af75c..8281e564 100644 --- a/lib/src/rmui_preparation_suggestors/constants.dart +++ b/lib/src/rmui_preparation_suggestors/constants.dart @@ -24,14 +24,18 @@ final rmuiBundleProd = ScriptToAdd(path: 'packages/react_material_ui/react-material-ui.umd.js'); /// The script pattern for finding react-dart JS scripts. -const reactJsScript = Script(pathSubpattern: r'packages/react/react\w*.js'); +const reactJsScript = Script( + pathSubpattern: r'packages/react/react\w*.js', + includeTrailingNewLine: false); /// A script that can be searched for via a script tag [pattern] for a /// specific path ([pathSubpattern]). class Script { final String pathSubpattern; + final bool includeTrailingNewLine; - const Script({required this.pathSubpattern}); + const Script( + {required this.pathSubpattern, this.includeTrailingNewLine = true}); /// A pattern for finding a script tag with a matching path, /// including preceding whitespace and any path prefix. @@ -43,7 +47,8 @@ class Script { RegExp get pattern => RegExp( r'(?[^\S\r\n]*).*)' + pathSubpattern + - r'".*'); + r'".*' + + (includeTrailingNewLine ? r'(?\n?)' : '')); @override String toString() => diff --git a/pubspec.yaml b/pubspec.yaml index 0bf527b2..5e46683c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,3 +55,4 @@ executables: intl_message_migration: sort_intl: unify_package_rename: + react_18_upgrade: diff --git a/test/executables/react_18_upgrade_test.dart b/test/executables/react_18_upgrade_test.dart new file mode 100644 index 00000000..85924a9b --- /dev/null +++ b/test/executables/react_18_upgrade_test.dart @@ -0,0 +1,206 @@ +// Copyright 2025 Workiva Inc. +// +// 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 'package:over_react_codemod/src/util/package_util.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; +import 'package:test_descriptor/test_descriptor.dart' as d; + +import 'mui_migration_test.dart'; + +void main() { + group('react_18_upgrade executable', () { + final react18CodemodScript = + p.join(findPackageRootFor(p.current), 'bin/react_18_upgrade.dart'); + + group('updates script tags', () { + testCodemod('dev', + script: react18CodemodScript, + input: d.dir('project', [ + d.file('dev.html', /*language=html*/ ''' + + '''), + d.file('dev_with_addons.html', /*language=html*/ ''' + + '''), + ]), + expectedOutput: d.dir('project', [ + d.file('dev.html', /*language=html*/ ''' + +'''), + d.file('dev_with_addons.html', /*language=html*/ ''' + +'''), + ]), + args: ['--yes-to-all']); + + testCodemod('prod', + script: react18CodemodScript, + input: d.dir('project', [ + d.file('prod.html', /*language=html*/ ''' + + '''), + d.file('prod_with_addons.html', /*language=html*/ ''' + + ''') + ]), + expectedOutput: d.dir('project', [ + d.file('prod.html', /*language=html*/ ''' + +'''), + d.file('prod_with_addons.html', /*language=html*/ ''' + +''') + ]), + args: ['--yes-to-all']); + }); + + group('updates link tags', () { + testCodemod('dev', + script: react18CodemodScript, + input: d.dir('project', [ + d.file('dev.html', /*language=html*/ ''' + + '''), + d.file('dev_with_addons.html', /*language=html*/ ''' + + '''), + ]), + expectedOutput: d.dir('project', [ + d.file('dev.html', /*language=html*/ ''' + +'''), + d.file('dev_with_addons.html', /*language=html*/ ''' + +'''), + ]), + args: ['--yes-to-all']); + + testCodemod('prod', + script: react18CodemodScript, + input: d.dir('project', [ + d.file('prod.html', /*language=html*/ ''' + + '''), + d.file('prod_with_addons.html', /*language=html*/ ''' + + ''') + ]), + expectedOutput: d.dir('project', [ + d.file('prod.html', /*language=html*/ ''' + +'''), + d.file('prod_with_addons.html', /*language=html*/ ''' + +''') + ]), + args: ['--yes-to-all']); + }); + + group('in Dart files', () { + testCodemod('list', + script: react18CodemodScript, + input: d.dir('project', [ + d.file('main.dart', /*language=dart*/ ''' + List _reactHtmlHeaders = const [ + '', + '', + '', + '', + ]; + ''') + ]), + expectedOutput: d.dir('project', [ + d.file('main.dart', /*language=dart*/ ''' + List _reactHtmlHeaders = const [ + '', + '', + ]; + ''') + ]), + args: ['--yes-to-all']); + + testCodemod('string const', + script: react18CodemodScript, + input: d.dir('project', [ + d.file('main.dart', ''' + const expectedWithReact = \'\'\' + + + + {{testName}} + + + + + + + {{testScript}} + + + + + \'\'\'; + ''') + ]), + expectedOutput: d.dir('project', [ + d.file('main.dart', ''' + const expectedWithReact = \'\'\' + + + + {{testName}} + + + + + + {{testScript}} + + + + + \'\'\'; + ''') + ]), + args: ['--yes-to-all']); + }); + + testCodemod('--fail-on-changes exits with 0 when no changes needed', + script: react18CodemodScript, + input: d.dir('project', [ + d.file('dev.html', /*language=html*/ ''' +'''), + d.file('dev_with_addons.html', /*language=html*/ ''' +'''), + d.file('prod.html', /*language=html*/ ''' +'''), + d.file('prod_with_addons.html', /*language=html*/ ''' +''') + ]), + expectedOutput: d.dir('project', [ + d.file('dev.html', /*language=html*/ ''' +'''), + d.file('dev_with_addons.html', /*language=html*/ ''' +'''), + d.file('prod.html', /*language=html*/ ''' +'''), + d.file('prod_with_addons.html', /*language=html*/ ''' +''') + ]), + args: ['--fail-on-changes'], body: (out, err) { + expect(out, contains('No changes needed.')); + }); + }); +} diff --git a/test/rmui_bundle_updater_suggestors/dart_script_updater_test.dart b/test/rmui_bundle_updater_suggestors/dart_script_updater_test.dart index de1f64eb..833df717 100644 --- a/test/rmui_bundle_updater_suggestors/dart_script_updater_test.dart +++ b/test/rmui_bundle_updater_suggestors/dart_script_updater_test.dart @@ -284,5 +284,141 @@ void main() { ''', ); }); + + test('string const', () async { + await testSuggestor( + expectedPatchCount: 2, + input: ''' + const expectedWithReact = \'\'\' + + + + {{testName}} + + + + + + + {{testScript}} + + + + + \'\'\'; + ''', + expectedOutput: ''' + const expectedWithReact = \'\'\' + + + + {{testName}} + + + + + + + {{testScript}} + + + + + \'\'\'; + ''', + ); + }); + + test('updateAttributes arg', () async { + final updateSuggestor = getSuggestorTester( + DartScriptUpdater(rmuiBundleDev, rmuiBundleDevUpdated, + updateAttributes: false), + ); + + await updateSuggestor( + expectedPatchCount: 2, + input: ''' + List _reactHtmlHeaders = const [ + '', + '', + ]; + ''', + expectedOutput: ''' + List _reactHtmlHeaders = const [ + '', + '', + ]; + ''', + ); + }); + + group('remove constructor', () { + final removeTagSuggestor = + getSuggestorTester(DartScriptUpdater.remove(rmuiBundleDev)); + + test('list', () async { + await removeTagSuggestor( + expectedPatchCount: 3, + input: ''' + List _reactHtmlHeaders = const [ + '', + '', + '', + '', + '', + ]; + ''', + expectedOutput: ''' + List _reactHtmlHeaders = const [ + '', + '', + ]; + ''', + ); + }); + + test('string const', () async { + await removeTagSuggestor( + expectedPatchCount: 1, + input: ''' + const expectedWithReact = \'\'\' + + + + {{testName}} + + + + + + + {{testScript}} + + + + + \'\'\'; + ''', + expectedOutput: ''' + const expectedWithReact = \'\'\' + + + + {{testName}} + + + + + + {{testScript}} + + + + + \'\'\'; + ''', + ); + }); + }); }); } diff --git a/test/rmui_bundle_updater_suggestors/html_script_updater_test.dart b/test/rmui_bundle_updater_suggestors/html_script_updater_test.dart index f7139991..a8378323 100644 --- a/test/rmui_bundle_updater_suggestors/html_script_updater_test.dart +++ b/test/rmui_bundle_updater_suggestors/html_script_updater_test.dart @@ -240,11 +240,11 @@ void main() { expectedPatchCount: 4, shouldDartfmtOutput: false, input: '' - '\n' + '\n' '\n' '', expectedOutput: '' - '\n' + '\n' '\n' '', ); @@ -264,5 +264,30 @@ void main() { '', ); }); + + test('removeTag arg', () async { + final removeTagSuggestor = + getSuggestorTester(HtmlScriptUpdater.remove(rmuiBundleDev)); + + await removeTagSuggestor( + shouldDartfmtOutput: false, + input: '' + '\n' + ' \n' + '\n' + '\n' + ' \n' + '\n' + '\n' + '\n' + '\n' + '', + expectedOutput: '' + '\n' + '\n' + '\n' + '', + ); + }); }); }