Implement multi-scheme animation name mapper for Owen Animation System
Some checks failed
CI/CD Pipeline / Test & Lint (16.x) (push) Has been cancelled
CI/CD Pipeline / Test & Lint (18.x) (push) Has been cancelled
CI/CD Pipeline / Test & Lint (20.x) (push) Has been cancelled
CI/CD Pipeline / Security Audit (push) Has been cancelled
CI/CD Pipeline / Release (push) Has been cancelled
Release / Validate Version (push) Has been cancelled
Release / Build and Test (push) Has been cancelled
Release / Create Release (push) Has been cancelled
Release / Publish to NPM (push) Has been cancelled
Release / Deploy Demo (push) Has been cancelled
Animation Processing Pipeline / Validate Animation Names (push) Has been cancelled
Animation Processing Pipeline / Process Blender Animation Assets (push) Has been cancelled
Animation Processing Pipeline / Update Animation Documentation (push) Has been cancelled
Animation Processing Pipeline / Deploy Animation Demo (push) Has been cancelled

- Added AnimationNameMapper class to handle conversion between different animation naming schemes (legacy, artist, hierarchical, semantic).
- Included methods for initialization, pattern matching, conversion, and validation of animation names.
- Developed comprehensive unit tests for the animation name converter and demo pages using Playwright.
- Created a Vite configuration for the demo application, including asset handling and optimization settings.
- Enhanced the demo with features for batch conversion, performance metrics, and responsive design.
This commit is contained in:
2025-05-24 05:18:13 +02:00
parent d513e80c07
commit ad8dbb95dd
55 changed files with 20060 additions and 6686 deletions

158
tests/demo.spec.js Normal file
View File

@ -0,0 +1,158 @@
import { test, expect } from '@playwright/test'
test.describe('Owen Animation System Demo', () => {
test.beforeEach(async ({ page }) => {
// Navigate to the demo page before each test
await page.goto('/')
})
test('should load the main demo page', async ({ page }) => {
// Check that the page title is correct
await expect(page).toHaveTitle(/Owen Animation System/)
// Check that the main heading is present
await expect(page.locator('h1')).toContainText('Owen Animation System')
// Check that the demo content is loaded
await expect(page.locator('.demo-content')).toBeVisible()
})
test('should display animation name converter', async ({ page }) => {
// Check that the converter section is present
await expect(page.locator('.converter-section')).toBeVisible()
// Check input fields
await expect(page.locator('#animationName')).toBeVisible()
await expect(page.locator('#sourceScheme')).toBeVisible()
await expect(page.locator('#targetScheme')).toBeVisible()
// Check convert button
await expect(page.locator('#convertBtn')).toBeVisible()
})
test('should convert animation names', async ({ page }) => {
// Fill in the converter form
await page.fill('#animationName', 'char_walk_01')
await page.selectOption('#sourceScheme', 'artist')
await page.selectOption('#targetScheme', 'semantic')
// Click convert button
await page.click('#convertBtn')
// Check that result is displayed
await expect(page.locator('#conversionResult')).toBeVisible()
await expect(page.locator('#conversionResult')).toContainText('character.movement.walk')
})
test('should validate animation names', async ({ page }) => {
// Test with invalid animation name
await page.fill('#animationName', 'invalid-name-123!@#')
await page.selectOption('#sourceScheme', 'semantic')
await page.click('#convertBtn')
// Should show validation error
await expect(page.locator('.error-message')).toBeVisible()
// Test with valid animation name
await page.fill('#animationName', 'character.idle.basic')
await page.click('#convertBtn')
// Should show success
await expect(page.locator('.success-message')).toBeVisible()
})
test('should show scheme comparison', async ({ page }) => {
// Check that scheme cards are present
await expect(page.locator('.scheme-card')).toHaveCount(4)
// Check that each scheme is represented
await expect(page.locator('.scheme-card')).toContainText(['Legacy', 'Artist', 'Hierarchical', 'Semantic'])
})
test('should handle batch conversion', async ({ page }) => {
// Click on batch conversion tab
await page.click('[data-tab="batch"]')
// Fill in batch input
const batchInput = [
'char_walk_01',
'char_run_02',
'prop_door_open'
].join('\n')
await page.fill('#batchInput', batchInput)
await page.selectOption('#batchSourceScheme', 'artist')
await page.selectOption('#batchTargetScheme', 'semantic')
// Click convert batch button
await page.click('#convertBatchBtn')
// Check that results are displayed
await expect(page.locator('#batchResults')).toBeVisible()
await expect(page.locator('.batch-result-item')).toHaveCount(3)
})
test('should export results', async ({ page }) => {
// Convert some animation names first
await page.fill('#animationName', 'char_walk_01')
await page.selectOption('#sourceScheme', 'artist')
await page.selectOption('#targetScheme', 'semantic')
await page.click('#convertBtn')
// Wait for result
await expect(page.locator('#conversionResult')).toBeVisible()
// Click export button
const downloadPromise = page.waitForEvent('download')
await page.click('#exportBtn')
const download = await downloadPromise
// Check that file was downloaded
expect(download.suggestedFilename()).toMatch(/animation-conversions.*\.json/)
})
test('should be responsive', async ({ page }) => {
// Test mobile viewport
await page.setViewportSize({ width: 375, height: 667 })
// Check that mobile navigation is present
await expect(page.locator('.mobile-nav')).toBeVisible()
// Check that converter still works
await page.fill('#animationName', 'test_animation')
await page.selectOption('#sourceScheme', 'legacy')
await page.selectOption('#targetScheme', 'semantic')
await page.click('#convertBtn')
await expect(page.locator('#conversionResult')).toBeVisible()
})
test('should handle errors gracefully', async ({ page }) => {
// Test with empty input
await page.click('#convertBtn')
await expect(page.locator('.error-message')).toContainText('Animation name is required')
// Test with same source and target scheme
await page.fill('#animationName', 'test_animation')
await page.selectOption('#sourceScheme', 'semantic')
await page.selectOption('#targetScheme', 'semantic')
await page.click('#convertBtn')
await expect(page.locator('.warning-message')).toContainText('Source and target schemes are the same')
})
test('should show performance metrics', async ({ page }) => {
// Check that performance section is present
await expect(page.locator('.performance-section')).toBeVisible()
// Convert some animations to generate metrics
await page.fill('#animationName', 'char_walk_01')
await page.selectOption('#sourceScheme', 'artist')
await page.selectOption('#targetScheme', 'semantic')
await page.click('#convertBtn')
// Check that metrics are updated
await expect(page.locator('.conversion-time')).toContainText(/\d+ms/)
await expect(page.locator('.total-conversions')).toContainText(/\d+/)
})
})

177
tests/pages.spec.js Normal file
View File

@ -0,0 +1,177 @@
import { test, expect } from '@playwright/test'
test.describe('Examples Page', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/examples.html')
})
test('should load examples page', async ({ page }) => {
await expect(page).toHaveTitle(/Examples/)
await expect(page.locator('h1')).toContainText('Integration Examples')
})
test('should display framework examples', async ({ page }) => {
// Check that example cards are present
await expect(page.locator('.example-card')).toHaveCount.greaterThan(3)
// Check specific framework examples
await expect(page.locator('.example-card')).toContainText(['React', 'Vue', 'Node.js'])
})
test('should copy code examples', async ({ page }) => {
// Click on a copy button
await page.click('.copy-button').first()
// Check that button text changes to "Copied!"
await expect(page.locator('.copy-button').first()).toContainText('Copied!')
})
test('should filter examples', async ({ page }) => {
// Click on React filter
await page.click('[data-filter="react"]')
// Check that only React examples are shown
await expect(page.locator('.example-card:visible')).toHaveCount(1)
await expect(page.locator('.example-card:visible')).toContainText('React')
})
})
test.describe('Comparison Page', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/comparison.html')
})
test('should load comparison page', async ({ page }) => {
await expect(page).toHaveTitle(/Comparison/)
await expect(page.locator('h1')).toContainText('Naming Scheme Comparison')
})
test('should display scheme cards', async ({ page }) => {
await expect(page.locator('.scheme-card')).toHaveCount(4)
// Check each scheme is present
const schemes = ['Legacy', 'Artist', 'Hierarchical', 'Semantic']
for (const scheme of schemes) {
await expect(page.locator('.scheme-card')).toContainText(scheme)
}
})
test('should show comparison table', async ({ page }) => {
await expect(page.locator('.comparison-table')).toBeVisible()
// Check table headers
await expect(page.locator('.comparison-table th')).toContainText(['Animation Name', 'Legacy', 'Artist', 'Hierarchical', 'Semantic'])
})
test('should filter comparison table', async ({ page }) => {
// Type in search box
await page.fill('.search-input', 'walk')
// Check that results are filtered
await expect(page.locator('.comparison-table tbody tr:visible')).toHaveCount.greaterThan(0)
await expect(page.locator('.comparison-table tbody tr:visible')).toContainText('walk')
})
test('should convert between schemes', async ({ page }) => {
// Use the conversion demo
await page.fill('.animation-input', 'char_walk_01')
await page.selectOption('#sourceSchemeSelect', 'artist')
await page.selectOption('#targetSchemeSelect', 'semantic')
// Check conversion result
await expect(page.locator('.conversion-result')).toBeVisible()
await expect(page.locator('.result-value')).toContainText('character.movement.walk')
})
})
test.describe('Interactive Playground', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/interactive.html')
})
test('should load interactive page', async ({ page }) => {
await expect(page).toHaveTitle(/Interactive/)
await expect(page.locator('h1')).toContainText('Interactive Playground')
})
test('should have functional controls', async ({ page }) => {
// Check that control sections are present
await expect(page.locator('.playground-controls')).toBeVisible()
await expect(page.locator('.playground-main')).toBeVisible()
await expect(page.locator('.performance-monitor')).toBeVisible()
})
test('should switch between tabs', async ({ page }) => {
// Click on different tabs
await page.click('[data-tab="converter"]')
await expect(page.locator('.tab-content[data-tab="converter"]')).toBeVisible()
await page.click('[data-tab="validator"]')
await expect(page.locator('.tab-content[data-tab="validator"]')).toBeVisible()
await page.click('[data-tab="generator"]')
await expect(page.locator('.tab-content[data-tab="generator"]')).toBeVisible()
})
test('should run code in playground', async ({ page }) => {
// Switch to code editor tab
await page.click('[data-tab="code"]')
// Clear and enter new code
await page.fill('.code-editor', `
const mapper = new AnimationNameMapper();
const result = mapper.convert('char_walk_01', 'artist', 'semantic');
console.log(result);
`)
// Run the code
await page.click('#runCodeBtn')
// Check output
await expect(page.locator('.output-panel')).toContainText('character.movement.walk')
})
test('should validate animation names in real-time', async ({ page }) => {
// Enter valid animation name
await page.fill('#playgroundAnimationName', 'character.idle.basic')
await page.selectOption('#playgroundScheme', 'semantic')
// Check validation indicator
await expect(page.locator('.validation-indicator')).toHaveClass(/success/)
// Enter invalid animation name
await page.fill('#playgroundAnimationName', 'invalid-name-123!')
// Check validation indicator shows error
await expect(page.locator('.validation-indicator')).toHaveClass(/error/)
})
test('should show performance metrics', async ({ page }) => {
// Perform some conversions
await page.fill('#playgroundAnimationName', 'char_walk_01')
await page.selectOption('#playgroundSourceScheme', 'artist')
await page.selectOption('#playgroundTargetScheme', 'semantic')
await page.click('#convertPlaygroundBtn')
// Check that performance metrics are updated
await expect(page.locator('.monitor-value')).toHaveCount.greaterThan(0)
await expect(page.locator('.conversion-time .monitor-value')).toContainText(/\d+/)
})
test('should save and load history', async ({ page }) => {
// Perform a conversion
await page.fill('#playgroundAnimationName', 'test_animation')
await page.selectOption('#playgroundSourceScheme', 'legacy')
await page.selectOption('#playgroundTargetScheme', 'semantic')
await page.click('#convertPlaygroundBtn')
// Check that history is updated
await expect(page.locator('.history-item')).toHaveCount.greaterThan(0)
// Click on history item to load it
await page.click('.history-item').first()
// Check that form is populated
await expect(page.locator('#playgroundAnimationName')).toHaveValue('test_animation')
})
})