@ornikar/kitt-universal 27.5.1-canary.3a59d4ae3422049417956fa37f27d2e8f5f84aba.0 → 27.5.1-canary.7b68cb8f8312c7736633882eefa6e43178ed4819.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,12 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- ## [27.5.1-canary.3a59d4ae3422049417956fa37f27d2e8f5f84aba.0](https://github.com/ornikar/kitt/compare/@ornikar/kitt-universal@27.5.0...@ornikar/kitt-universal@27.5.1-canary.3a59d4ae3422049417956fa37f27d2e8f5f84aba.0) (2025-09-16)
6
+ ## [27.5.1-canary.7b68cb8f8312c7736633882eefa6e43178ed4819.0](https://github.com/ornikar/kitt/compare/@ornikar/kitt-universal@27.5.0...@ornikar/kitt-universal@27.5.1-canary.7b68cb8f8312c7736633882eefa6e43178ed4819.0) (2025-09-16)
7
7
 
8
8
 
9
9
  ### Features
10
10
 
11
11
  * generate csf2 codemod ([3a59d4a](https://github.com/ornikar/kitt/commit/3a59d4ae3422049417956fa37f27d2e8f5f84aba))
12
+ * improve codemod ([7b68cb8](https://github.com/ornikar/kitt/commit/7b68cb8f8312c7736633882eefa6e43178ed4819))
12
13
  * **kitt-universal:** generate codemod instructions OSE-19616 ([#2682](https://github.com/ornikar/kitt/issues/2682)) ([291e65b](https://github.com/ornikar/kitt/commit/291e65bb3692ccb17d6d78abea0eeea1cf7c20a5))
13
14
  * **rebranding:** review `TabBar` OSE-18003 ([#2687](https://github.com/ornikar/kitt/issues/2687)) ([a757d23](https://github.com/ornikar/kitt/commit/a757d237394d9fe65508d11998f6d5f6971b5bbd))
14
15
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ornikar/kitt-universal",
3
- "version": "27.5.1-canary.3a59d4ae3422049417956fa37f27d2e8f5f84aba.0",
3
+ "version": "27.5.1-canary.7b68cb8f8312c7736633882eefa6e43178ed4819.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "directory": "@ornikar/kitt-universal",
@@ -8,7 +8,7 @@ import { NotificationEnablerScreenView } from './NotificationEnablerScreenView';
8
8
  export default {
9
9
  title: 'LNA/shared/components',
10
10
  component: NotificationEnablerScreenView,
11
- decorators: [AnotherDecorator, NativeStoryDecorator],
11
+ decorators: [NativeStoryDecorator, AnotherDecorator],
12
12
  } satisfies ComponentMeta<typeof NotificationEnablerScreenView>;
13
13
 
14
14
  export const NotificationEnablerScreenStory: ComponentStory<typeof NotificationEnablerScreenView> = () => (
@@ -0,0 +1,77 @@
1
+ import { action } from '@storybook/addon-actions';
2
+ import { storiesOf } from '@storybook/react-native';
3
+ import { ApolloError } from '@apollo/client';
4
+ import { StoryDecorator } from '@ornikar/kitt-universal';
5
+ import { NotificationProvider } from '@ornikar/react-notification';
6
+ import {
7
+ ThirdPartyServiceStatusEnum,
8
+ ThirdPartyServiceTypeEnum,
9
+ } from '@ornikar/learner-apps-shared/src/__generated__/globalTypes';
10
+ import {
11
+ mockLearningChaptersEmptyError,
12
+ mockLearningChaptersWithCourses,
13
+ mockLearningChaptersWithCoursesQueryBuilder,
14
+ } from '@ornikar/learner-apps-shared/src/shared/apollo/mocks/learningChaptersWithCoursesMock';
15
+ import {
16
+ getThirdPartyService,
17
+ mockThirdPartyServiceQueryBuilder,
18
+ } from '@ornikar/learner-apps-shared/src/shared/apollo/mocks/thirdPartyService';
19
+ import { ApolloDecorator } from '../../../../../../../storybook/decorators/ApolloDecorator';
20
+ import {
21
+ DrivingDashboardLearningChaptersModalView,
22
+ LearningChaptersModalBodyWrapper,
23
+ } from './DrivingDashboardLearningChaptersModalView';
24
+
25
+ storiesOf('LAS/driving/dashboard/components/DrivingDashboardLearningChaptersModal', module)
26
+ .addDecorator(StoryDecorator as any)
27
+ .addDecorator(ApolloDecorator)
28
+ .add('Default', () => <DrivingDashboardLearningChaptersModalView visible onClose={action('onClose')} />, {
29
+ apollo: {
30
+ mocks: [
31
+ mockThirdPartyServiceQueryBuilder(
32
+ ThirdPartyServiceTypeEnum.NORTHPASS,
33
+ getThirdPartyService(ThirdPartyServiceTypeEnum.NORTHPASS, ThirdPartyServiceStatusEnum.UP),
34
+ ),
35
+ mockLearningChaptersWithCoursesQueryBuilder(mockLearningChaptersWithCourses),
36
+ ],
37
+ },
38
+ jest: {
39
+ ignore: true,
40
+ },
41
+ })
42
+ .add('Empty chapters', () => <LearningChaptersModalBodyWrapper visible />, {
43
+ apollo: {
44
+ mocks: [
45
+ mockThirdPartyServiceQueryBuilder(
46
+ ThirdPartyServiceTypeEnum.NORTHPASS,
47
+ getThirdPartyService(ThirdPartyServiceTypeEnum.NORTHPASS, ThirdPartyServiceStatusEnum.UP),
48
+ ),
49
+ mockLearningChaptersWithCoursesQueryBuilder(mockLearningChaptersEmptyError),
50
+ ],
51
+ },
52
+ jest: {
53
+ ignore: true,
54
+ },
55
+ })
56
+ .add(
57
+ 'Error',
58
+ () => (
59
+ <NotificationProvider>
60
+ <DrivingDashboardLearningChaptersModalView visible onClose={action('onClose')} />
61
+ </NotificationProvider>
62
+ ),
63
+ {
64
+ apollo: {
65
+ mocks: [
66
+ mockThirdPartyServiceQueryBuilder(
67
+ ThirdPartyServiceTypeEnum.NORTHPASS,
68
+ getThirdPartyService(ThirdPartyServiceTypeEnum.NORTHPASS, ThirdPartyServiceStatusEnum.DOWN),
69
+ ),
70
+ mockLearningChaptersWithCoursesQueryBuilder(mockLearningChaptersEmptyError, new ApolloError({})),
71
+ ],
72
+ },
73
+ jest: {
74
+ ignore: true,
75
+ },
76
+ },
77
+ );
@@ -0,0 +1,92 @@
1
+ import { action } from '@storybook/addon-actions';
2
+ import type { ComponentMeta, ComponentStory } from '@storybook/react-native';
3
+ import { ApolloError } from '@apollo/client';
4
+ import { StoryDecorator } from '@ornikar/kitt-universal';
5
+ import { NotificationProvider } from '@ornikar/react-notification';
6
+ import {
7
+ ThirdPartyServiceStatusEnum,
8
+ ThirdPartyServiceTypeEnum,
9
+ } from '@ornikar/learner-apps-shared/src/__generated__/globalTypes';
10
+ import {
11
+ mockLearningChaptersEmptyError,
12
+ mockLearningChaptersWithCourses,
13
+ mockLearningChaptersWithCoursesQueryBuilder,
14
+ } from '@ornikar/learner-apps-shared/src/shared/apollo/mocks/learningChaptersWithCoursesMock';
15
+ import {
16
+ getThirdPartyService,
17
+ mockThirdPartyServiceQueryBuilder,
18
+ } from '@ornikar/learner-apps-shared/src/shared/apollo/mocks/thirdPartyService';
19
+ import { ApolloDecorator } from '../../../../../../../storybook/decorators/ApolloDecorator';
20
+ import {
21
+ DrivingDashboardLearningChaptersModalView,
22
+ LearningChaptersModalBodyWrapper,
23
+ } from './DrivingDashboardLearningChaptersModalView';
24
+
25
+ export default {
26
+ title: 'LAS/driving/dashboard/components/DrivingDashboardLearningChaptersModal',
27
+ component: DrivingDashboardLearningChaptersModalView,
28
+ decorators: [StoryDecorator, ApolloDecorator],
29
+ } satisfies ComponentMeta<typeof DrivingDashboardLearningChaptersModalView>;
30
+
31
+ export const DefaultStory: ComponentStory<typeof DrivingDashboardLearningChaptersModalView> = () => (
32
+ <DrivingDashboardLearningChaptersModalView visible onClose={action('onClose')} />
33
+ );
34
+ DefaultStory.storyName = 'Default';
35
+
36
+ DefaultStory.parameters = {
37
+ apollo: {
38
+ mocks: [
39
+ mockThirdPartyServiceQueryBuilder(
40
+ ThirdPartyServiceTypeEnum.NORTHPASS,
41
+ getThirdPartyService(ThirdPartyServiceTypeEnum.NORTHPASS, ThirdPartyServiceStatusEnum.UP),
42
+ ),
43
+ mockLearningChaptersWithCoursesQueryBuilder(mockLearningChaptersWithCourses),
44
+ ],
45
+ },
46
+ jest: {
47
+ ignore: true,
48
+ },
49
+ };
50
+
51
+ export const EmptyChaptersStory: ComponentStory<typeof DrivingDashboardLearningChaptersModalView> = () => (
52
+ <LearningChaptersModalBodyWrapper visible />
53
+ );
54
+ EmptyChaptersStory.storyName = 'Empty chapters';
55
+
56
+ EmptyChaptersStory.parameters = {
57
+ apollo: {
58
+ mocks: [
59
+ mockThirdPartyServiceQueryBuilder(
60
+ ThirdPartyServiceTypeEnum.NORTHPASS,
61
+ getThirdPartyService(ThirdPartyServiceTypeEnum.NORTHPASS, ThirdPartyServiceStatusEnum.UP),
62
+ ),
63
+ mockLearningChaptersWithCoursesQueryBuilder(mockLearningChaptersEmptyError),
64
+ ],
65
+ },
66
+ jest: {
67
+ ignore: true,
68
+ },
69
+ };
70
+
71
+ export const ErrorStory: ComponentStory<typeof DrivingDashboardLearningChaptersModalView> = () => (
72
+ <NotificationProvider>
73
+ <DrivingDashboardLearningChaptersModalView visible onClose={action('onClose')} />
74
+ </NotificationProvider>
75
+ );
76
+
77
+ ErrorStory.storyName = 'Error';
78
+
79
+ ErrorStory.parameters = {
80
+ apollo: {
81
+ mocks: [
82
+ mockThirdPartyServiceQueryBuilder(
83
+ ThirdPartyServiceTypeEnum.NORTHPASS,
84
+ getThirdPartyService(ThirdPartyServiceTypeEnum.NORTHPASS, ThirdPartyServiceStatusEnum.DOWN),
85
+ ),
86
+ mockLearningChaptersWithCoursesQueryBuilder(mockLearningChaptersEmptyError, new ApolloError({})),
87
+ ],
88
+ },
89
+ jest: {
90
+ ignore: true,
91
+ },
92
+ };
@@ -3,7 +3,7 @@
3
3
  jest.autoMockOff();
4
4
  const { defineTest } = require('jscodeshift/dist/testUtils');
5
5
 
6
- const tests = ['default', 'decorator', 'parameters'];
6
+ const tests = ['default', 'decorator', 'parameters', 'new'];
7
7
 
8
8
  describe('CSF1-CSF2 Migration', () => {
9
9
  tests.forEach((test) => defineTest(__dirname, 'csf1-csf2', null, `csf1-csf2/${test}`, { parser: 'tsx' }));
@@ -41,27 +41,23 @@ module.exports = async function transformer(fileInfo, api) {
41
41
  hasTransformation = true;
42
42
  }
43
43
 
44
- // Find storiesOf() calls that end with .add() - look for the expression statement that contains them
44
+ // Find storiesOf() chains - look for any expression that contains storiesOf
45
45
  const storiesOfStatements = root.find(j.ExpressionStatement).filter((path) => {
46
46
  const expression = path.node.expression;
47
- if (expression.type !== 'CallExpression' || !expression.callee) return false;
48
-
49
- // Check if this ends with .add()
50
- if (expression.callee.type === 'MemberExpression' && expression.callee.property.name === 'add') {
51
- // Walk up the chain to find storiesOf
52
- let current = expression.callee.object;
53
- while (current) {
54
- if (current.type === 'CallExpression' && current.callee && current.callee.name === 'storiesOf') {
55
- return true;
56
- }
57
- if (current.type === 'CallExpression' && current.callee && current.callee.type === 'MemberExpression') {
58
- current = current.callee.object;
59
- } else {
60
- break;
61
- }
47
+ if (expression.type !== 'CallExpression') return false;
48
+
49
+ // Look for storiesOf anywhere in the call chain
50
+ function containsStoriesOf(node) {
51
+ if (node.type === 'CallExpression' && node.callee && node.callee.name === 'storiesOf') {
52
+ return true;
53
+ }
54
+ if (node.type === 'CallExpression' && node.callee && node.callee.type === 'MemberExpression') {
55
+ return containsStoriesOf(node.callee.object);
62
56
  }
57
+ return false;
63
58
  }
64
- return false;
59
+
60
+ return containsStoriesOf(expression);
65
61
  });
66
62
 
67
63
  if (storiesOfStatements.length > 0) {
@@ -71,35 +67,62 @@ module.exports = async function transformer(fileInfo, api) {
71
67
  storiesOfStatements.forEach((path) => {
72
68
  const expression = path.node.expression;
73
69
 
74
- // Extract information from the .add() call
75
- let storyName = expression.arguments[0].value;
76
- const componentFunction = expression.arguments[1];
77
-
78
- // Walk up the chain to find storiesOf call and collect decorators and parameters
79
- let storiesOfCall = null;
80
- const decorators = [];
81
- let parameters = null;
82
- let current = expression.callee.object;
70
+ // Parse the entire call chain to extract all stories, decorators, and parameters
71
+ function parseStoriesChain(node) {
72
+ const stories = [];
73
+ const decorators = [];
74
+ let globalParameters = null;
75
+ let storiesOfCall = null;
76
+
77
+ function walkChain(current) {
78
+ if (current.type === 'CallExpression' && current.callee) {
79
+ if (current.callee.name === 'storiesOf') {
80
+ // Found the root storiesOf call
81
+ storiesOfCall = current;
82
+ } else if (current.callee.type === 'MemberExpression') {
83
+ const methodName = current.callee.property.name;
84
+
85
+ if (methodName === 'add') {
86
+ // Extract story information
87
+ const storyName = current.arguments[0].value;
88
+ const componentFunction = current.arguments[1];
89
+ const storyParameters = current.arguments[2]; // Third argument contains parameters
90
+
91
+ stories.push({
92
+ name: storyName,
93
+ component: componentFunction,
94
+ parameters: storyParameters,
95
+ });
96
+ } else if (methodName === 'addDecorator') {
97
+ let decorator = current.arguments[0];
98
+ // Remove 'as any' type assertions from decorators
99
+ if (decorator.type === 'TSAsExpression' && decorator.typeAnnotation.type === 'TSAnyKeyword') {
100
+ decorator = decorator.expression;
101
+ }
102
+ decorators.push(decorator); // Collect decorators in order
103
+ } else if (methodName === 'addParameters') {
104
+ globalParameters = current.arguments[0];
105
+ }
83
106
 
84
- while (current) {
85
- if (current.type === 'CallExpression' && current.callee && current.callee.name === 'storiesOf') {
86
- storiesOfCall = current;
87
- break;
88
- }
89
- if (current.type === 'CallExpression' && current.callee && current.callee.type === 'MemberExpression') {
90
- const methodName = current.callee.property.name;
91
- if (methodName === 'addDecorator') {
92
- decorators.push(current.arguments[0]);
93
- } else if (methodName === 'addParameters') {
94
- parameters = current.arguments[0];
107
+ // Continue walking up the chain
108
+ walkChain(current.callee.object);
109
+ }
95
110
  }
96
- current = current.callee.object;
97
- } else {
98
- break;
99
111
  }
112
+
113
+ walkChain(node);
114
+
115
+ return {
116
+ stories: stories.reverse(), // Reverse to get correct order
117
+ decorators: decorators.reverse(), // Reverse to get correct order
118
+ globalParameters,
119
+ storiesOfCall,
120
+ };
100
121
  }
101
122
 
102
- if (!storiesOfCall) return;
123
+ const { stories, decorators, globalParameters, storiesOfCall } = parseStoriesChain(expression);
124
+
125
+ if (!storiesOfCall || stories.length === 0) return;
103
126
 
104
127
  let storyTitle = storiesOfCall.arguments[0].value;
105
128
 
@@ -108,11 +131,7 @@ module.exports = async function transformer(fileInfo, api) {
108
131
  storyTitle = 'LNA/authentication/pages';
109
132
  }
110
133
 
111
- if (storyName === 'WelcomePageView') {
112
- storyName = 'WelcomePage';
113
- }
114
-
115
- // Extract component name from the component function
134
+ // Extract component name from the first story's component function
116
135
  let componentName = 'Component'; // Default fallback
117
136
 
118
137
  function findComponentInJSX(jsxElement) {
@@ -157,20 +176,28 @@ module.exports = async function transformer(fileInfo, api) {
157
176
  return allComponents.length > 0 ? allComponents[0] : jsxElement.openingElement.name.name;
158
177
  }
159
178
 
160
- if (componentFunction.type === 'ArrowFunctionExpression') {
161
- if (componentFunction.body.type === 'JSXElement') {
162
- componentName = findComponentInJSX(componentFunction.body);
163
- } else if (componentFunction.body.type === 'BlockStatement') {
164
- // Look for JSX return statement
165
- const returnStatement = componentFunction.body.body.find(
166
- (stmt) => stmt.type === 'ReturnStatement' && stmt.argument && stmt.argument.type === 'JSXElement',
167
- );
168
- if (returnStatement) {
169
- componentName = findComponentInJSX(returnStatement.argument);
179
+ function extractComponentName(componentFunction) {
180
+ if (componentFunction.type === 'ArrowFunctionExpression') {
181
+ if (componentFunction.body.type === 'JSXElement') {
182
+ return findComponentInJSX(componentFunction.body);
183
+ } else if (componentFunction.body.type === 'BlockStatement') {
184
+ // Look for JSX return statement
185
+ const returnStatement = componentFunction.body.body.find(
186
+ (stmt) => stmt.type === 'ReturnStatement' && stmt.argument && stmt.argument.type === 'JSXElement',
187
+ );
188
+ if (returnStatement) {
189
+ return findComponentInJSX(returnStatement.argument);
190
+ }
170
191
  }
192
+ } else if (componentFunction.type === 'FunctionExpression') {
193
+ // Similar logic for function expressions if needed
171
194
  }
172
- } else if (componentFunction.type === 'FunctionExpression') {
173
- // Similar logic for function expressions if needed
195
+ return 'Component';
196
+ }
197
+
198
+ // Use the first story to determine the main component
199
+ if (stories.length > 0) {
200
+ componentName = extractComponentName(stories[0].component);
174
201
  }
175
202
 
176
203
  // Create default export properties in the exact order needed
@@ -182,8 +209,8 @@ module.exports = async function transformer(fileInfo, api) {
182
209
  props.push(j.property('init', j.identifier('decorators'), j.arrayExpression(decorators)));
183
210
  }
184
211
 
185
- if (parameters) {
186
- props.push(j.property('init', j.identifier('parameters'), parameters));
212
+ if (globalParameters) {
213
+ props.push(j.property('init', j.identifier('parameters'), globalParameters));
187
214
  }
188
215
 
189
216
  const objExpression = j.objectExpression(props);
@@ -198,30 +225,61 @@ module.exports = async function transformer(fileInfo, api) {
198
225
  ),
199
226
  );
200
227
 
201
- // Create named export for the story with TypeScript type annotation
202
- const storyExportName = storyName.replace(/\s+/g, '') + 'Story';
203
- const storyIdentifier = j.identifier(storyExportName);
204
- storyIdentifier.typeAnnotation = j.tsTypeAnnotation(
205
- j.tsTypeReference(
206
- j.identifier('ComponentStory'),
207
- j.tsTypeParameterInstantiation([j.tsTypeQuery(j.identifier(componentName))]),
208
- ),
209
- );
228
+ // Add the default export first
229
+ newExports.push(defaultExport);
210
230
 
211
- const namedExport = j.exportNamedDeclaration(
212
- j.variableDeclaration('const', [j.variableDeclarator(storyIdentifier, componentFunction)]),
213
- );
231
+ // Process each story
232
+ stories.forEach((story) => {
233
+ let storyName = story.name;
214
234
 
215
- // Create storyName assignment
216
- const storyNameAssignment = j.expressionStatement(
217
- j.assignmentExpression(
218
- '=',
219
- j.memberExpression(j.identifier(storyExportName), j.identifier('storyName')),
220
- j.literal(storyName),
221
- ),
222
- );
235
+ // Normalize story names
236
+ if (storyName === 'WelcomePageView') {
237
+ storyName = 'WelcomePage';
238
+ }
223
239
 
224
- newExports.push(defaultExport, namedExport, storyNameAssignment);
240
+ // Create named export for the story with TypeScript type annotation
241
+ // For single words, preserve casing; for multi-word names, capitalize each word
242
+ const storyExportName = storyName.includes(' ')
243
+ ? storyName
244
+ .split(/\s+/)
245
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
246
+ .join('') + 'Story'
247
+ : storyName + 'Story';
248
+ const storyIdentifier = j.identifier(storyExportName);
249
+ storyIdentifier.typeAnnotation = j.tsTypeAnnotation(
250
+ j.tsTypeReference(
251
+ j.identifier('ComponentStory'),
252
+ j.tsTypeParameterInstantiation([j.tsTypeQuery(j.identifier(componentName))]),
253
+ ),
254
+ );
255
+
256
+ const namedExport = j.exportNamedDeclaration(
257
+ j.variableDeclaration('const', [j.variableDeclarator(storyIdentifier, story.component)]),
258
+ );
259
+
260
+ // Create storyName assignment
261
+ const storyNameAssignment = j.expressionStatement(
262
+ j.assignmentExpression(
263
+ '=',
264
+ j.memberExpression(j.identifier(storyExportName), j.identifier('storyName')),
265
+ j.literal(storyName),
266
+ ),
267
+ );
268
+
269
+ newExports.push(namedExport, storyNameAssignment);
270
+
271
+ // Add parameters if the story has them
272
+ if (story.parameters) {
273
+ const parametersAssignment = j.expressionStatement(
274
+ j.assignmentExpression(
275
+ '=',
276
+ j.memberExpression(j.identifier(storyExportName), j.identifier('parameters')),
277
+ story.parameters,
278
+ ),
279
+ );
280
+ newExports.push(parametersAssignment);
281
+ }
282
+ });
225
283
 
226
284
  // Remove the original statement
227
285
  j(path).remove();