gsap-test — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited gsap-test (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Flow: gsap-setup → gsap-animate → gsap-optimise → gsap-test
Companion: This skill covers testing and debugging recipes. No API overlap — unique to this plugin.
import { describe, it, expect, vi, afterEach } from 'vitest'
import { mount } from '@vue/test-utils'
import { gsap } from 'gsap'
afterEach(() => gsap.globalTimeline.clear())
describe('MyComponent animations', () => {
it('cleans up all animations on unmount', async () => {
const wrapper = mount(MyComponent)
await new Promise(r => setTimeout(r, 100))
const before = gsap.globalTimeline.getChildren().length
await wrapper.unmount()
expect(gsap.globalTimeline.getChildren().length).toBeLessThan(before)
})
it('cleans up ScrollTriggers on unmount', async () => {
const { ScrollTrigger } = await import('gsap/ScrollTrigger')
gsap.registerPlugin(ScrollTrigger)
const wrapper = mount(MyComponent)
await new Promise(r => setTimeout(r, 100))
await wrapper.unmount()
expect(ScrollTrigger.getAll().length).toBe(0)
})
it('respects reduced motion', async () => {
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation(query => ({
matches: query === '(prefers-reduced-motion: reduce)',
media: query, addEventListener: vi.fn(),
})),
})
const wrapper = mount(MyComponent)
await new Promise(r => setTimeout(r, 100))
expect(getComputedStyle(wrapper.find('.animated').element).visibility).not.toBe('hidden')
})
})// plugins/gsap-test-hooks.client.js (dev/test only)
if (process.env.NODE_ENV !== 'production') {
window.completeAllAnimations = () => {
gsap.globalTimeline.progress(1)
ScrollTrigger.getAll().forEach(st => st.scroll(st.end))
}
window.skipAnimations = () => gsap.globalTimeline.timeScale(100)
}test('page renders correctly after animations', async ({ page }) => {
await page.goto('/')
await page.evaluate(() => window.completeAllAnimations?.())
await page.waitForTimeout(500)
await expect(page).toHaveScreenshot('homepage.png', { fullPage: true })
})
test('page works with reduced motion', async ({ page }) => {
await page.emulateMedia({ reducedMotion: 'reduce' })
await page.goto('/')
await expect(page.locator('.hero')).toBeVisible()
})if (window.location.search.includes('skip-animations')) {
gsap.globalTimeline.timeScale(100)
}// Markers (dev only)
ScrollTrigger.defaults({ markers: true })
// Console patterns
console.log('ScrollTriggers:', ScrollTrigger.getAll())
console.log('Active tweens:', gsap.globalTimeline.getChildren().length)
// Memory leak check — after navigating away:
// Both should be 0 after leaving the page
console.log('Remaining tweens:', gsap.globalTimeline.getChildren().length)
console.log('Remaining ScrollTriggers:', ScrollTrigger.getAll().length)Markers & Debug
markers: true removedconsole.log in animation callbacksnullTargetWarn: false in gsap.config for productionPerformance
will-change released after animations completeAccessibility
prefers-reduced-motion: reduceCleanup
gsap.globalTimeline.getChildren().length is 0 after leavingScrollTrigger.getAll().length is 0 after leavingScrollTrigger
ScrollTrigger.refresh() after lazy images and fontsVisual QA
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.