freemium-survey-components 2.0.480 → 2.0.482

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.
@@ -0,0 +1,368 @@
1
+ name: PR Template Validation
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, edited, synchronize]
6
+
7
+ permissions:
8
+ pull-requests: write
9
+ issues: write
10
+ contents: read
11
+
12
+ jobs:
13
+ validate-template:
14
+ runs-on: ['self-hosted', 'Linux']
15
+ name: Validate PR Template Sections
16
+
17
+ steps:
18
+ - name: Check if PR is from a bot
19
+ id: bot-check
20
+ uses: actions/github-script@v7
21
+ with:
22
+ script: |
23
+ const pr = context.payload.pull_request;
24
+ const author = pr.user.login;
25
+ const authorType = pr.user.type;
26
+
27
+ console.log('=== Bot Detection ===');
28
+ console.log(`PR Author: ${author}`);
29
+ console.log(`Author Type: ${authorType}`);
30
+
31
+ const botUsers = [
32
+ 'dependabot[bot]',
33
+ 'dependabot-preview[bot]',
34
+ 'renovate[bot]',
35
+ 'github-actions[bot]',
36
+ 'mergify[bot]',
37
+ 'snyk-bot'
38
+ ];
39
+
40
+ const isBot =
41
+ botUsers.includes(author) ||
42
+ author.endsWith('[bot]') ||
43
+ authorType === 'Bot';
44
+
45
+ const isDependencyPR =
46
+ pr.title.toLowerCase().includes('bump') ||
47
+ pr.title.toLowerCase().includes('update dependencies') ||
48
+ pr.title.toLowerCase().includes('chore(deps)');
49
+
50
+ const shouldSkip = isBot || isDependencyPR;
51
+
52
+ console.log(`Is Bot: ${isBot}`);
53
+ console.log(`Is Dependency PR: ${isDependencyPR}`);
54
+ console.log(`Should Skip Validation: ${shouldSkip}`);
55
+
56
+ if (shouldSkip) {
57
+ console.log('⏭️ Skipping PR template validation for automated PR');
58
+
59
+ const botSkipMarker = '<!-- pr-bot-skip-marker -->';
60
+ const comments = await github.paginate(github.rest.issues.listComments, {
61
+ owner: context.repo.owner,
62
+ repo: context.repo.repo,
63
+ issue_number: context.issue.number,
64
+ per_page: 100
65
+ });
66
+
67
+ const existingComment = comments.find(c => c.body.includes(botSkipMarker));
68
+ if (!existingComment) {
69
+ await github.rest.issues.createComment({
70
+ owner: context.repo.owner,
71
+ repo: context.repo.repo,
72
+ issue_number: context.issue.number,
73
+ body: `${botSkipMarker}\n🤖 **Automated PR Detected** - Skipping PR template validation.\n\nThis PR appears to be from a bot or automated process. Template validation is not required.`
74
+ });
75
+ console.log('Created bot skip comment');
76
+ } else {
77
+ console.log('Bot skip comment already exists, skipping duplicate');
78
+ }
79
+ }
80
+
81
+ return shouldSkip;
82
+
83
+ - name: Validate PR Template Completion
84
+ if: steps.bot-check.outputs.result != 'true'
85
+ uses: actions/github-script@v7
86
+ with:
87
+ github-token: ${{ secrets.RUNWAYCI_GITHUB_PAT_TOKEN }}
88
+ script: |
89
+ const prBody = context.payload.pull_request.body || '';
90
+ const prTitle = context.payload.pull_request.title || '';
91
+ const errors = [];
92
+ const warnings = [];
93
+
94
+ console.log('=== Starting PR Template Validation ===');
95
+ console.log(`PR #${context.issue.number}: ${prTitle}`);
96
+
97
+ // 0. Validate PR Title (must match commit message format)
98
+ console.log('Checking PR Title format...');
99
+ const validTypes = ['feat', 'fix', 'chore', 'docs', 'refactor', 'test', 'perf', 'ci', 'build', 'style', 'revert'];
100
+ const titleRegex = /^(feat|fix|chore|docs|refactor|test|perf|ci|build|style|revert):\s*\[(MP-\d+|FS-\d+|FSURVEY-\d+|DEVREL-\d+|MISC|Docs)\]\s+\S.*$/;
101
+ const hasTitleFormat = titleRegex.test(prTitle);
102
+
103
+ if (!hasTitleFormat) {
104
+ errors.push(`**PR Title**: Invalid format. Required format: \`type: [TICKET-ID] description\`
105
+
106
+ **Valid types**: ${validTypes.join(', ')}
107
+ **Valid tickets**: MP-xxxx, FS-xxxx, FSURVEY-xxxx, DEVREL-xxxx, MISC, Docs
108
+
109
+ **Examples**:
110
+ - ✅ \`feat: [MP-12345] Add Matrix question variant\`
111
+ - ✅ \`feat: [FS-1000] Add Dropdown component\`
112
+ - ✅ \`feat: [FSURVEY-2024] Add survey rendering enhancement\`
113
+ - ✅ \`fix: [MISC] Fix Consent focus trap\`
114
+ - ✅ \`chore: [DEVREL-4720] Update dependencies\`
115
+ - ❌ \`Add matrix question\` (missing type and ticket)
116
+ - ❌ \`[MP-12345] Add feature\` (missing type prefix)
117
+
118
+ **Your title**: \`${prTitle}\``);
119
+ } else {
120
+ const titleMatch = prTitle.match(titleRegex);
121
+ console.log(`✓ PR Title format valid: ${titleMatch[1]} - ${titleMatch[2]}`);
122
+ }
123
+
124
+ // 1. Validate Type of Change (at least one checkbox selected)
125
+ console.log('Checking Type of Change...');
126
+ const typeRegex = /## Type of Change[\s\S]*?(?=##|$)/;
127
+ const typeSection = prBody.match(typeRegex)?.[0] || '';
128
+ const hasTypeSelected = /- \[x\]/i.test(typeSection);
129
+
130
+ if (!hasTypeSelected) {
131
+ errors.push('**Type of Change**: No type selected. Please check at least one box (Feature, Bugfix, Refactor, etc.).');
132
+ } else {
133
+ console.log('✓ Type of Change selected');
134
+ }
135
+
136
+ // 2. Validate Ticket Reference (must have valid ticket ID)
137
+ console.log('Checking Ticket Reference...');
138
+ const ticketRegex = /-\s*\*\*Ticket ID\*\*:\s*\[(MP-\d+|FS-\d+|FSURVEY-\d+|DEVREL-\d+|MISC|Docs)\]/;
139
+ const hasTicketId = ticketRegex.test(prBody);
140
+
141
+ if (!hasTicketId) {
142
+ errors.push('**Ticket Reference**: Missing or invalid ticket ID. Required format: `[MP-xxxx]`, `[FS-xxxx]`, `[FSURVEY-xxxx]`, `[DEVREL-xxxx]`, `[MISC]`, or `[Docs]`');
143
+ } else {
144
+ const ticketMatch = prBody.match(ticketRegex);
145
+ console.log(`✓ Ticket ID found: ${ticketMatch[1]}`);
146
+ }
147
+
148
+ // 3. Validate Changes Made (minimum 3 bullet points with content)
149
+ console.log('Checking Changes Made...');
150
+ const changesRegex = /## Changes Made[\s\S]*?(?=##|$)/;
151
+ const changesSection = prBody.match(changesRegex)?.[0] || '';
152
+ const changesBullets = (changesSection.match(/^-\s+.+$/gm) || [])
153
+ .map(line => line.replace(/^-\s+/, '').trim())
154
+ .filter(content =>
155
+ content.length > 0 &&
156
+ !/^Change\s+\d+:?\s*$/i.test(content)
157
+ );
158
+
159
+ if (changesBullets.length < 3) {
160
+ errors.push(`**Changes Made**: Insufficient items listed (found ${changesBullets.length}). Please provide at least 3 bullet points describing your changes.`);
161
+ } else {
162
+ console.log(`✓ Changes Made: ${changesBullets.length} item(s) listed (minimum 3 met)`);
163
+ }
164
+
165
+ // 4. Validate Affected Areas (at least one checkbox selected)
166
+ console.log('Checking Affected Areas...');
167
+ const affectedRegex = /## Affected Areas[\s\S]*?(?=##|$)/;
168
+ const affectedSection = prBody.match(affectedRegex)?.[0] || '';
169
+ const hasAreaSelected = /- \[x\]/i.test(affectedSection);
170
+
171
+ if (!hasAreaSelected) {
172
+ errors.push('**Affected Areas**: No area selected. Please check at least one affected area (Source, Build/Bundling, CI/CD, Dependencies).');
173
+ } else {
174
+ console.log('✓ Affected Areas selected');
175
+ }
176
+
177
+ // 5. Validate Breaking Changes (must select exactly one option)
178
+ console.log('Checking Breaking Changes...');
179
+ const breakingRegex = /## Breaking Changes[\s\S]*?(?=##|$)/;
180
+ const breakingSection = prBody.match(breakingRegex)?.[0] || '';
181
+ const breakingSelections = breakingSection.match(
182
+ /^- \[[xX]\]\s+(Yes,?\s+this PR introduces breaking changes|No breaking changes.*)$/gim
183
+ ) || [];
184
+ const hasBreakingSelection = breakingSelections.length === 1;
185
+
186
+ if (!hasBreakingSelection) {
187
+ errors.push('**Breaking Changes**: Select exactly one option (Yes or No).');
188
+ } else {
189
+ console.log('✓ Breaking Changes option selected');
190
+ }
191
+
192
+ // 6. If breaking changes = yes, require description
193
+ const hasBreakingYes = /^-\s*\[[xX]\]\s+Yes,?\s+this PR introduces breaking changes/im.test(breakingSection);
194
+ if (hasBreakingYes) {
195
+ console.log('Breaking changes detected, checking for description...');
196
+ const descriptionPart = breakingSection
197
+ .replace(/^## Breaking Changes.*$/m, '')
198
+ .replace(/^- \[[ xX]\]\s+Yes,?\s+this PR introduces breaking changes.*$/m, '')
199
+ .replace(/^- \[[ xX]\]\s+No breaking changes.*$/m, '')
200
+ .replace(/<!---[\s\S]*?-->/g, '')
201
+ .trim();
202
+ const hasDescription = descriptionPart.length > 50;
203
+
204
+ if (!hasDescription) {
205
+ errors.push('**Breaking Changes**: You selected "Yes" but did not describe the breaking changes. Please provide details and migration steps.');
206
+ } else {
207
+ console.log('✓ Breaking changes description provided');
208
+ }
209
+ }
210
+
211
+ // 7. Validate Testing section (test steps required)
212
+ console.log('Checking Testing section...');
213
+ const testingRegex = /## How Has This Been Tested\?[\s\S]*?(?=##|$)/;
214
+ const testingSection = prBody.match(testingRegex)?.[0] || '';
215
+ const testStepsContent = testingSection
216
+ .replace(/\*\*Test Steps:\*\*/g, '')
217
+ .replace(/<!---[\s\S]*?-->/g, '')
218
+ .trim();
219
+ const numberedSteps = testStepsContent.match(/^\d+\.\s+.+$/gm) || [];
220
+ const hasTestSteps = numberedSteps.some(
221
+ step => !/^\d+\.\s+Step\s+\d+\s*$/i.test(step.trim())
222
+ );
223
+
224
+ if (!hasTestSteps) {
225
+ warnings.push('**Testing**: Test steps are missing or contain only placeholders. Please describe how you tested your changes (steps 1, 2, 3...).');
226
+ } else {
227
+ console.log('✓ Test steps provided');
228
+ }
229
+
230
+ // 8. Validate SDET Guidance (must be filled)
231
+ console.log('Checking SDET Guidance...');
232
+ const sdetRegex = /## What should the SDET know\?[\s\S]*?(?=##|$)/;
233
+ const sdetSection = prBody.match(sdetRegex)?.[0] || '';
234
+ const sdetContent = sdetSection
235
+ .split('## What should the SDET know?')[1] || '';
236
+ const sdetCleanContent = sdetContent
237
+ .replace(/<!---[\s\S]*?-->/g, '')
238
+ .replace(/^-\s+Test\s+scenario\s+\d+:?\s*$/gim, '')
239
+ .replace(/^-\s+Edge\s+case(\s+to\s+check)?:?\s*$/gim, '')
240
+ .replace(/^-\s+Focus\s+area:?\s*$/gim, '')
241
+ .trim();
242
+ const hasSdetGuidance = sdetCleanContent.length > 30;
243
+
244
+ if (!hasSdetGuidance) {
245
+ warnings.push('**SDET Guidance**: Please provide testing guidance for the QA team (test scenarios, edge cases, focus areas).');
246
+ } else {
247
+ console.log('✓ SDET guidance provided');
248
+ }
249
+
250
+ // 9. Check Screenshots for UI changes
251
+ console.log('Checking for UI changes...');
252
+ const files = await github.paginate(github.rest.pulls.listFiles, {
253
+ owner: context.repo.owner,
254
+ repo: context.repo.repo,
255
+ pull_number: context.issue.number,
256
+ per_page: 100
257
+ });
258
+
259
+ const uiFiles = files.filter(file =>
260
+ file.filename.endsWith('.tsx') &&
261
+ (
262
+ file.filename.startsWith('src/components/') ||
263
+ file.filename.startsWith('src/survey/') ||
264
+ file.filename.startsWith('src/stories/')
265
+ )
266
+ );
267
+
268
+ if (uiFiles.length > 0) {
269
+ console.log(`UI changes detected in ${uiFiles.length} files`);
270
+ const screenshotsRegex = /## Screenshots\/Videos[\s\S]*?(?=##|$)/;
271
+ const screenshotsSection = prBody.match(screenshotsRegex)?.[0] || '';
272
+ const hasScreenshots = /!\[.*?\]\(.*?\)|https?:\/\/\S+\.(png|jpg|jpeg|gif|mp4|webm|mov)(?:\?\S+)?|https?:\/\/github\.com\/user-attachments\/assets\/[^\s)]+/i.test(screenshotsSection);
273
+
274
+ if (!hasScreenshots) {
275
+ warnings.push(`**Screenshots/Videos**: UI changes detected in ${uiFiles.length} file(s) but no screenshots provided. Please consider adding before/after screenshots or a Storybook / Chromatic link.`);
276
+ } else {
277
+ console.log('✓ Screenshots provided for UI changes');
278
+ }
279
+ } else {
280
+ console.log('No UI changes detected');
281
+ }
282
+
283
+ // Generate validation report
284
+ console.log(`\nValidation Results: ${errors.length} errors, ${warnings.length} warnings`);
285
+
286
+ const docsRef = context.payload.pull_request.head.sha;
287
+ const repoUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}`;
288
+ const guideUrl = `${repoUrl}/blob/${docsRef}/.github/pr_validation_guide.md`;
289
+ const templateUrl = `${repoUrl}/blob/${docsRef}/.github/pull_request_template.md`;
290
+
291
+ const reportMarker = '<!-- pr-template-validation-report -->';
292
+ let comment = `${reportMarker}\n## 🔍 PR Template Validation Report\n\n`;
293
+
294
+ if (errors.length > 0) {
295
+ comment += '### ❌ Required Sections Missing\n\n';
296
+ comment += 'The following **required sections** must be completed:\n\n';
297
+ comment += errors.map(e => `- ${e}`).join('\n');
298
+ comment += '\n\n**⚠️ Action Required**: Please edit your PR description to include all required information.\n\n';
299
+ comment += `📖 **Verify against the rules:** [PR Validation Guide](${guideUrl})\n\n`;
300
+ comment += '---\n\n';
301
+ }
302
+
303
+ if (warnings.length > 0) {
304
+ comment += '### ⚠️ Recommended Improvements\n\n';
305
+ comment += 'Please consider adding:\n\n';
306
+ comment += warnings.map(w => `- ${w}`).join('\n');
307
+ comment += '\n\n---\n\n';
308
+ }
309
+
310
+ if (errors.length === 0 && warnings.length === 0) {
311
+ comment = `${reportMarker}\n## ✅ PR Template Complete\n\n`;
312
+ comment += 'All required sections are properly filled. Great job! 🎉\n';
313
+ }
314
+
315
+ // Add helpful footer
316
+ comment += '\n---\n\n';
317
+ comment += '<details>\n';
318
+ comment += '<summary>📋 PR Template Requirements</summary>\n\n';
319
+ comment += '**Required Sections:**\n';
320
+ comment += '- Type of Change (select at least one)\n';
321
+ comment += '- Ticket Reference (valid ticket ID)\n';
322
+ comment += '- Changes Made (minimum 3 items)\n';
323
+ comment += '- Affected Areas (select at least one)\n';
324
+ comment += '- Breaking Changes (select Yes/No)\n\n';
325
+ comment += '**Recommended Sections:**\n';
326
+ comment += '- Testing (test steps)\n';
327
+ comment += '- SDET Guidance (QA testing guidance)\n';
328
+ comment += '- Screenshots (if UI changes)\n\n';
329
+ comment += `📖 [PR Validation Guide](${guideUrl}) — full rules, examples, and troubleshooting\n`;
330
+ comment += `📝 [Pull Request Template](${templateUrl}) — the template structure\n`;
331
+ comment += '</details>\n';
332
+
333
+ // Post or update comment
334
+ const comments = await github.paginate(github.rest.issues.listComments, {
335
+ owner: context.repo.owner,
336
+ repo: context.repo.repo,
337
+ issue_number: context.issue.number,
338
+ per_page: 100
339
+ });
340
+
341
+ const botComment = comments.find(c =>
342
+ c.body.includes(reportMarker)
343
+ );
344
+
345
+ if (botComment) {
346
+ await github.rest.issues.updateComment({
347
+ owner: context.repo.owner,
348
+ repo: context.repo.repo,
349
+ comment_id: botComment.id,
350
+ body: comment
351
+ });
352
+ console.log('Updated existing validation comment');
353
+ } else {
354
+ await github.rest.issues.createComment({
355
+ owner: context.repo.owner,
356
+ repo: context.repo.repo,
357
+ issue_number: context.issue.number,
358
+ body: comment
359
+ });
360
+ console.log('Created new validation comment');
361
+ }
362
+
363
+ // Fail the check if there are errors (blocks merge with branch protection)
364
+ if (errors.length > 0) {
365
+ core.setFailed(`PR template validation failed: ${errors.length} required section(s) missing or incomplete`);
366
+ } else {
367
+ console.log('✅ PR template validation passed!');
368
+ }
package/lib/bundle.css CHANGED
@@ -4937,8 +4937,10 @@ div.surveyserv-widget-container.compact-container-style .fsc-radio-group button.
4937
4937
  }
4938
4938
  .facebook-wrapper .fb-question-container {
4939
4939
  align-self: flex-start;
4940
+ box-sizing: border-box;
4941
+ width: max-content;
4940
4942
  max-width: 90%;
4941
- min-width: 150px;
4943
+ min-width: 0;
4942
4944
  display: flex;
4943
4945
  flex-direction: column;
4944
4946
  pointer-events: none;
@@ -5086,16 +5088,44 @@ div.surveyserv-widget-container.compact-container-style .fsc-radio-group button.
5086
5088
  hyphens: auto;
5087
5089
  }
5088
5090
  .facebook-wrapper .choices {
5091
+ box-sizing: border-box;
5089
5092
  display: flex;
5090
5093
  flex-direction: column;
5091
5094
  gap: 6px;
5092
5095
  margin-top: 0px;
5093
5096
  list-style-type: none;
5094
5097
  padding: 0;
5098
+ min-width: 0;
5099
+ width: 100%;
5095
5100
  overflow-wrap: break-word;
5096
5101
  word-break: break-word;
5097
5102
  hyphens: auto;
5098
5103
  }
5104
+ .facebook-wrapper .choices .ellipsis-wrapper {
5105
+ min-width: 0;
5106
+ max-width: 100%;
5107
+ overflow: hidden;
5108
+ text-overflow: ellipsis;
5109
+ white-space: nowrap;
5110
+ word-break: normal;
5111
+ overflow-wrap: normal;
5112
+ }
5113
+ .facebook-wrapper .choices .star-rating-wrapper {
5114
+ display: flex;
5115
+ align-items: center;
5116
+ min-width: 0;
5117
+ max-width: 100%;
5118
+ overflow: hidden;
5119
+ }
5120
+ .facebook-wrapper .choices .star-rating-wrapper .star-rating-label {
5121
+ flex: 1 1 0;
5122
+ min-width: 0;
5123
+ overflow: hidden;
5124
+ text-overflow: ellipsis;
5125
+ white-space: nowrap;
5126
+ word-break: normal;
5127
+ overflow-wrap: normal;
5128
+ }
5099
5129
  .facebook-wrapper .choices svg:not(#star) {
5100
5130
  width: 25px;
5101
5131
  height: 25px;
@@ -5113,10 +5143,9 @@ div.surveyserv-widget-container.compact-container-style .fsc-radio-group button.
5113
5143
  padding-right: 15px;
5114
5144
  }
5115
5145
  .facebook-wrapper .choices.boolean-choices:has(.choice:nth-child(2):last-child) {
5116
- flex-direction: row;
5146
+ flex-direction: column;
5117
5147
  }
5118
5148
  .facebook-wrapper .choices.boolean-choices:has(.choice:nth-child(2):last-child) .boolean-choice {
5119
- width: calc(80% - 16px);
5120
5149
  border-radius: 8px;
5121
5150
  }
5122
5151
  .facebook-wrapper .choices.boolean-choices .boolean-choice {
@@ -5127,15 +5156,19 @@ div.surveyserv-widget-container.compact-container-style .fsc-radio-group button.
5127
5156
  font-weight: 500;
5128
5157
  gap: 8px;
5129
5158
  width: 100%;
5130
- padding: 6px 4px;
5159
+ min-width: 0;
5160
+ max-width: 100%;
5161
+ padding: 6px;
5131
5162
  border: none;
5132
5163
  display: flex;
5133
5164
  justify-content: center;
5134
5165
  align-items: center;
5166
+ overflow: hidden;
5167
+ height: 32px;
5135
5168
  }
5136
5169
  .facebook-wrapper .choices.dropdown-choices {
5137
5170
  display: grid;
5138
- grid-template-columns: repeat(2, 1fr);
5171
+ grid-template-columns: minmax(0, 1fr);
5139
5172
  padding: 0 12px;
5140
5173
  gap: 8px;
5141
5174
  }
@@ -5143,23 +5176,27 @@ div.surveyserv-widget-container.compact-container-style .fsc-radio-group button.
5143
5176
  border-radius: 8px;
5144
5177
  padding: 2px;
5145
5178
  display: flex;
5179
+ min-width: 0;
5180
+ max-width: 100%;
5181
+ overflow: hidden;
5146
5182
  text-align: center;
5147
5183
  align-items: center;
5148
5184
  background-color: #e2e5ea !important;
5149
5185
  }
5150
- .facebook-wrapper .choices.dropdown-choices .dropdown-choice:first-child {
5151
- grid-column: span 2;
5152
- }
5153
5186
  .facebook-wrapper .choices.dropdown-choices .dropdown-choice button {
5154
5187
  font-size: 14px;
5155
5188
  font-weight: 500;
5156
5189
  gap: 8px;
5157
5190
  width: 100%;
5191
+ min-width: 0;
5192
+ max-width: 100%;
5158
5193
  padding: 6px;
5159
5194
  border: none;
5160
5195
  display: flex;
5161
5196
  justify-content: center;
5162
5197
  align-items: center;
5198
+ overflow: hidden;
5199
+ height: 32px;
5163
5200
  }
5164
5201
  .facebook-wrapper .choices.dropdown-choices .dropdown-choice button .fb-star-rating-wrapper {
5165
5202
  align-items: center;