use-storybook-testing-library.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. "use strict";
  2. /**
  3. * @fileoverview Do not use testing library directly on stories
  4. * @author Yann Braga
  5. */
  6. const ast_1 = require("../utils/ast");
  7. const constants_1 = require("../utils/constants");
  8. const create_storybook_rule_1 = require("../utils/create-storybook-rule");
  9. module.exports = (0, create_storybook_rule_1.createStorybookRule)({
  10. name: 'use-storybook-testing-library',
  11. defaultOptions: [],
  12. meta: {
  13. type: 'suggestion',
  14. fixable: 'code',
  15. hasSuggestions: true,
  16. docs: {
  17. description: 'Do not use testing-library directly on stories',
  18. categories: [constants_1.CategoryId.ADDON_INTERACTIONS, constants_1.CategoryId.RECOMMENDED],
  19. recommended: 'error',
  20. },
  21. schema: [],
  22. messages: {
  23. updateImports: 'Update imports',
  24. dontUseTestingLibraryDirectly: 'Do not use `{{library}}` directly in the story. You should import the functions from `@storybook/testing-library` instead.',
  25. },
  26. },
  27. create(context) {
  28. // variables should be defined here
  29. //----------------------------------------------------------------------
  30. // Helpers
  31. //----------------------------------------------------------------------
  32. const getRangeWithoutQuotes = (source) => {
  33. return [
  34. // Not sure how to improve this. If I use node.source.range
  35. // it will eat the quotes and we do not want to specify whether the quotes are single or double
  36. source.range[0] + 1,
  37. source.range[1] - 1,
  38. ];
  39. };
  40. const hasDefaultImport = (specifiers) => specifiers.find((s) => (0, ast_1.isImportDefaultSpecifier)(s));
  41. const getSpecifiers = (node) => {
  42. const { specifiers } = node;
  43. if (!specifiers[0]) {
  44. return null;
  45. }
  46. const start = specifiers[0].range[0];
  47. const previousSpecifier = specifiers[specifiers.length - 1];
  48. if (!previousSpecifier) {
  49. return null;
  50. }
  51. let end = previousSpecifier.range[1];
  52. // this weird hack is necessary because the specifier range
  53. // does not include the closing brace:
  54. //
  55. // import foo, { bar } from 'baz';
  56. // ^ ^ end
  57. const fullText = context.getSourceCode().text;
  58. const importEnd = node.range[1];
  59. const closingBrace = fullText.indexOf('}', end - 1);
  60. if (closingBrace > -1 && closingBrace <= importEnd) {
  61. end = closingBrace + 1;
  62. }
  63. const text = fullText.substring(start, end);
  64. return { range: [start, end], text };
  65. };
  66. const fixSpecifiers = (specifiersText) => {
  67. const flattened = specifiersText
  68. .replace('{', '')
  69. .replace('}', '')
  70. .replace(/\s\s+/g, ' ')
  71. .trim();
  72. return `{ ${flattened} }`;
  73. };
  74. //----------------------------------------------------------------------
  75. // Public
  76. //----------------------------------------------------------------------
  77. return {
  78. ImportDeclaration(node) {
  79. if (node.source.value.includes('@testing-library')) {
  80. context.report({
  81. node,
  82. messageId: 'dontUseTestingLibraryDirectly',
  83. data: {
  84. library: node.source.value,
  85. },
  86. *fix(fixer) {
  87. yield fixer.replaceTextRange(getRangeWithoutQuotes(node.source), '@storybook/testing-library');
  88. if (hasDefaultImport(node.specifiers)) {
  89. const specifiers = getSpecifiers(node);
  90. if (specifiers) {
  91. const { range, text } = specifiers;
  92. yield fixer.replaceTextRange(range, fixSpecifiers(text));
  93. }
  94. }
  95. },
  96. suggest: [
  97. {
  98. messageId: 'updateImports',
  99. *fix(fixer) {
  100. yield fixer.replaceTextRange(getRangeWithoutQuotes(node.source), '@storybook/testing-library');
  101. if (hasDefaultImport(node.specifiers)) {
  102. const specifiers = getSpecifiers(node);
  103. if (specifiers) {
  104. const { range, text } = specifiers;
  105. yield fixer.replaceTextRange(range, fixSpecifiers(text));
  106. }
  107. }
  108. },
  109. },
  110. ],
  111. });
  112. }
  113. },
  114. };
  115. },
  116. });