Add Dynamic Poll Web Part with core functionality and styling

- Initialize Gulp build process with gulpfile.js
- Create package.json for project dependencies and scripts
- Add TypeScript declarations for SCSS modules
- Implement DynamicPollWebPart with property pane configuration
- Create PollDataService for SharePoint list interactions
- Develop core polling logic in PollCore.ts
- Define types for polls and answers in PollTypes.ts
- Create React components for DynamicPoll UI
- Add localization support for English and German
- Implement unit tests for core polling functionality
- Set up TypeScript and TSLint configurations
This commit is contained in:
Torsten Brendgen
2026-08-23 22:51:10 +02:00
commit ca41674188
30 changed files with 955 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
import { calculatePollResults, findUserAnswer, selectActivePoll } from '../src/webparts/dynamicPoll/PollCore';
import { IPoll, IPollAnswer } from '../src/webparts/dynamicPoll/PollTypes';
let passed: number = 0;
function assert(name: string, condition: boolean): void {
if (!condition) { throw new Error('FAILED: ' + name); }
passed++; console.log('OK:', name);
}
function poll(value: any): IPoll { return value as IPoll; } // tslint:disable-line:no-any
const now: Date = new Date('2026-08-23T12:00:00Z');
const selected: IPoll = selectActivePoll([
poll({ id: 1, question: 'old', options: ['A'], isActive: true,
startDate: new Date('2026-08-20T00:00:00Z'), endDate: new Date('2026-08-22T00:00:00Z') }),
poll({ id: 2, question: 'current', options: ['A', 'B'], isActive: true,
startDate: new Date('2026-08-23T00:00:00Z'), endDate: new Date('2026-08-24T00:00:00Z') }),
poll({ id: 3, question: 'future', options: ['A'], isActive: true,
startDate: new Date('2026-08-24T00:00:00Z') })
], now);
assert('current poll is selected by activation window', !!selected && selected.id === 2);
const answers: IPollAnswer[] = [
{ id: 1, userKey: 'one@contoso.local', answer: 'A' },
{ id: 2, userKey: 'two@contoso.local', answer: 'B' },
{ id: 3, userKey: 'three@contoso.local', answer: 'A' },
{ id: 4, userKey: 'legacy@contoso.local', answer: 'Removed option' }
];
const results = calculatePollResults(['A', 'B'], answers);
assert('votes are counted per configured option', results[0].votes === 2 && results[1].votes === 1);
assert('invalid legacy answer is excluded from percentage', results[0].percentage === 67 && results[1].percentage === 33);
assert('user lookup is case insensitive', findUserAnswer(answers, 'ONE@CONTOSO.LOCAL') === 'A');
assert('unknown user has no answer', findUserAnswer(answers, 'unknown@contoso.local') === '');
console.log('DynamicPoll core tests passed:', passed);
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"outDir": "../temp/tests",
"skipLibCheck": true,
"typeRoots": ["../node_modules/@types", "../node_modules/@microsoft"],
"types": ["es6-promise"]
},
"files": [
"../src/webparts/dynamicPoll/PollTypes.ts",
"../src/webparts/dynamicPoll/PollCore.ts",
"PollCore.test.ts"
]
}
+32
View File
@@ -0,0 +1,32 @@
'use strict';
var fs = require('fs');
var path = require('path');
var root = path.resolve(__dirname, '..');
var passed = 0;
function read(file) { return fs.readFileSync(path.join(root, file), 'utf8'); }
function json(file) { return JSON.parse(read(file)); }
function assert(condition, message) { if (!condition) { throw new Error(message); } passed++; }
var packageJson = json('package.json');
var solution = json('config/package-solution.json').solution;
var manifest = json('src/webparts/dynamicPoll/DynamicPollWebPart.manifest.json');
var webPart = read('src/webparts/dynamicPoll/DynamicPollWebPart.ts');
var service = read('src/webparts/dynamicPoll/PollDataService.ts');
var component = read('src/webparts/dynamicPoll/components/DynamicPoll.tsx');
var styles = read('src/webparts/dynamicPoll/components/DynamicPoll.module.scss');
assert(packageJson.version === '1.0.0' && solution.version === '1.0.0.0', 'Version mismatch.');
assert(solution.includeClientSideAssets === true && solution.skipFeatureDeployment === true, 'Central deployment missing.');
assert(manifest.componentType === 'WebPart', 'Component is not a web part.');
assert(webPart.indexOf("PropertyPaneDropdown('pollListId'") >= 0 && webPart.indexOf("PropertyPaneDropdown('answerListId'") >= 0,
'List selection is missing.');
assert(service.indexOf("'PollId': pollId") >= 0 && service.indexOf("'Answer': answer") >= 0, 'Vote persistence is missing.');
assert(service.indexOf("new Error('ALREADY_VOTED')") >= 0, 'Duplicate vote protection is missing.');
assert(service.indexOf("new Error('POLL_NOT_ACTIVE')") >= 0, 'Active poll and answer validation is missing.');
assert(component.indexOf("type='radio'") >= 0 && component.indexOf("role='progressbar'") >= 0,
'Accessible voting or results UI is missing.');
assert(fs.existsSync(path.join(root, 'deployment/Initialize-DynamicPollLists.ps1')), 'Provisioning script is missing.');
['neutralPrimary', 'neutralLight', 'themePrimary', 'themeDark', 'white'].forEach(function (slot) {
assert(styles.indexOf('[theme: ' + slot + ', default:') >= 0, 'Theme slot missing: ' + slot);
});
console.log('DynamicPoll project validation passed:', passed);