-
Notifications
You must be signed in to change notification settings - Fork 21
Animation on hover w/ Gleam Logo #2807
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 15 commits
52b2af2
6859dfc
175ee69
c864859
c4ab10f
ea04fe0
a20cf9f
6cc3d5b
0335cf1
72c9864
4ecb22e
0ac1be3
cf62de1
1d67c55
d007423
88fa7b8
257025c
e951505
d798652
8052924
8603f7e
5853d88
53f6401
711a877
733209d
63cdade
f6bfef5
a9d58af
662128f
1ea7dda
2b86774
ad8dbb2
75d66f3
9c91bd9
bac2acc
8e9e0d0
d8cfe02
cfe0f6a
bcf6a32
df4458e
88b1138
dd80705
092fcd5
3757657
6ad0f70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<div {{did-insert this.handleDidInsert}} {{will-destroy this.cleanupRive}} style={{this.containerStyle}} ...attributes> | ||
{{! The canvas will be inserted here by Rive }} | ||
</div> |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,108 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import Component from '@glimmer/component'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { tracked } from '@glimmer/tracking'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { action } from '@ember/object'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { Rive, Layout, Fit } from '@rive-app/canvas'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
interface GleamLogoSignature { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Element: HTMLDivElement; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Args: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
height: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's try to avoid needing a number to be passed in and see if we can just have it fit the container / parent element |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Blocks: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default: []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export default class GleamLogoComponent extends Component<GleamLogoSignature> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@tracked riveInstance: Rive | null = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
container: HTMLElement | null = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainAdd unit tests for this component. The component is completely missing test coverage according to static analysis. Consider adding unit tests that verify the initialization, animation behaviors, and cleanup processes. I can help you implement comprehensive tests for this component that verify:
Would you like me to provide a test implementation? 🌐 Web query:
💡 Result: Writing Unit Tests for Ember Components with Rive AnimationsOverview Testing Ember components that embed Rive animations requires a combination of standard Ember component testing strategies and considerations specific to third-party rendering handled by Rive. Because Rive animations are rendered in a Recommended Approach 1. Use Rendering or Integration TestsWhile "unit tests" in Ember often refer to logic-only tests, components—especially those outputting dynamic UI like Rive animations—are best tested with rendering (also known as integration) tests. These let you render the component, interact with it, and assert on the DOM or called behaviors[1][5][7][8].
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | my-rive-component', function(hooks) {
setupRenderingTest(hooks);
test('it renders the canvas for Rive', async function(assert) {
await render(hbs`<MyRiveComponent />`);
assert.dom('canvas').exists('Rive canvas is rendered');
});
}); [1][5][7] 2. Mock Rive Animation LogicExternal libraries like Rive often require DOM interaction and asynchronous file loading. To keep your tests fast and reliable:
// In your test setup
let riveInstanceMock = {
on: sinon.spy(),
play: sinon.spy(),
pause: sinon.spy()
};
window.Rive = sinon.stub().returns(riveInstanceMock);
3. Assert DOM and Behavioral Effects
test('it reacts to Rive animation event', async function(assert) {
await render(hbs`<MyRiveComponent />`);
// Simulate event
riveInstanceMock.on.withArgs('riveevent').callArgWith(1, { data: { name: 'someEvent' } });
// Assert component behavior
assert.dom('.some-result').hasText('Expected Result');
}); 4. Handle Asynchronous BehaviorMany Rive interactions (loading, animation events) are async. Use 5. Tips and Good Practices
Example Workflow
Summary
This test strategy ensures that your components work as intended, even when integrating complex, third-party animation libraries like Rive. Citations:
Add integration tests for GleamLogoComponent The
Feel free to follow Ember’s testing guide for rendering tests and let me know if you’d like a full example test implementation. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
animationInterval: number | null = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
rohitpaulk marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
get containerStyle(): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return `height: ${this.args.height}px; width: auto;`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@action | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cleanupRive() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (this.animationInterval) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
clearInterval(this.animationInterval); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.animationInterval = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (this.riveInstance) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.riveInstance.stop(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.riveInstance = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@action | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
handleDidInsert(element: HTMLDivElement) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.container = element; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const canvas = document.createElement('canvas'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Set initial canvas size for high quality | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
canvas.width = 400; // Base size for quality | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
canvas.height = 400; // Will adjust based on container | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Let the canvas scale naturally within its container | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
canvas.style.width = '100%'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Arpan-206 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
canvas.style.height = '100%'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Arpan-206 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
canvas.style.display = 'block'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
element.appendChild(canvas); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.riveInstance = new Rive({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
src: '/assets/animations/gleam_logo_animation.riv', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
canvas: canvas, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
layout: new Layout({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fit: Fit.Contain, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
autoplay: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onLoad: () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.log('onLoad'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (this.riveInstance) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const stateMachines = this.riveInstance.stateMachineNames; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (stateMachines?.includes('State Machine 2')) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Play first time immediately | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.riveInstance.play('State Machine 2'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Play State Machine 3 after 1 second to reset | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
setTimeout(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (this.riveInstance) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.riveInstance.play('State Machine 3'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, 1000); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add error handling for setTimeout cleanup. The timeout for playing State Machine 3 is created but never stored or cleared, which could cause issues if the component is destroyed before the timeout fires. @tracked riveInstance: Rive | null = null;
container: HTMLElement | null = null;
animationInterval: number | null = null;
+resetTimeout: number | null = null;
@action
cleanupRive() {
if (this.animationInterval) {
clearInterval(this.animationInterval);
this.animationInterval = null;
}
+
+ if (this.resetTimeout) {
+ clearTimeout(this.resetTimeout);
+ this.resetTimeout = null;
+ }
// Rest of the method...
}
// Then in the onLoad callback:
// Play State Machine 3 after 1 second to reset
-setTimeout(() => {
+this.resetTimeout = window.setTimeout(() => {
if (this.riveInstance) {
this.riveInstance.play('State Machine 3');
}
}, 1000); 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Set up hover state machine | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
canvas.addEventListener('mouseenter', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (this.riveInstance) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.riveInstance.play('State Machine 1'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
canvas.addEventListener('mouseleave', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (this.riveInstance) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.riveInstance.play('State Machine 1'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clean up event listeners to prevent memory leaks. Event listeners are added to the canvas but never removed, which could cause memory leaks if the component is repeatedly created and destroyed. @tracked riveInstance: Rive | null = null;
container: HTMLElement | null = null;
animationInterval: number | null = null;
+canvasElement: HTMLCanvasElement | null = null;
@action
cleanupRive() {
// Existing cleanup code...
+
+ // Remove event listeners
+ if (this.canvasElement) {
+ this.canvasElement.removeEventListener('mouseenter', this.handleMouseEnter);
+ this.canvasElement.removeEventListener('mouseleave', this.handleMouseLeave);
+ this.canvasElement = null;
+ }
}
@action
handleDidInsert(element: HTMLDivElement) {
// Existing code...
try {
const canvas = document.createElement('canvas');
+ this.canvasElement = canvas;
// Existing setup code...
// Create bound event handlers
+ this.handleMouseEnter = this.handleMouseEnter.bind(this);
+ this.handleMouseLeave = this.handleMouseLeave.bind(this);
// Set up hover state machine
- canvas.addEventListener('mouseenter', () => {
- if (this.riveInstance) {
- this.riveInstance.play('State Machine 1');
- }
- });
+ canvas.addEventListener('mouseenter', this.handleMouseEnter);
- canvas.addEventListener('mouseleave', () => {
- if (this.riveInstance) {
- this.riveInstance.play('State Machine 1');
- }
- });
+ canvas.addEventListener('mouseleave', this.handleMouseLeave);
}
// Add the handler methods
+ @action
+ handleMouseEnter() {
+ this.playStateMachine('State Machine 1');
+ }
+
+ @action
+ handleMouseLeave() {
+ this.playStateMachine('State Machine 1');
+ }
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} catch (error: unknown) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.error('Error setting up Rive:', error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's report this to sentry, don't just swallow and ignore |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainImplement better error handling for Rive initialization failures. Currently, errors are only logged to the console, which isn't sufficient for production environments. Consider sending these errors to a monitoring service or providing a fallback UI. 🌐 Web query:
💡 Result: Ember Error Reporting and Error Handling PatternsEmber does not provide a built-in error reporting service directly integrated into the framework, but it does offer several built-in hooks and patterns for handling errors in components and across the application. Global Error Handling Ember provides a global error handling mechanism through the Recommended Error Handling Patterns
Component Error Handling While Ember does not offer component-level error “boundaries” like React, you can use try-catch logic within component actions or lifecycle hooks. For more advanced use, some community addons and patterns facilitate error propagation and management within components[6]. Error Reporting For reporting errors to external services, Ember supports integration with many third-party error monitoring platforms, such as Sentry, Rollbar, and Airbrake:
Community Addons There are several Ember addons to enhance error handling and reporting. For example, Summary Table: Ember Error Handling Approaches
ConclusionEmber does not have an all-in-one built-in error reporting service, but it provides robust primitives ( Citations:
Enhance Rive Initialization Error Handling Ember doesn’t include a turnkey error-reporting service, but you can hook into its global error APIs (or integrate Sentry/Rollbar) and fall back to a safe UI when initialization fails. • Location to address: • Suggested changes: } catch (error: unknown) {
- console.error('Error setting up Rive:', error);
+ // 1) Report the error globally or to an external service
+ Ember.onerror?.(error);
+ // If using Sentry add-on:
+ // import { captureException } from '@sentry/ember';
+ // captureException(error);
+
+ // 2) Trigger a fallback UI state
+ this.hasLoadError = true;
} • Next steps:
📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
declare module '@glint/environment-ember-loose/registry' { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export default interface Registry { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GleamLogo: typeof GleamLogoComponent; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
{{#if (eq @variant "color")}} | ||
{{#if (and (eq @language.slug "gleam") (eq @variant "color"))}} | ||
<GleamLogo @height={{144}} ...attributes /> | ||
{{else if (eq @variant "color")}} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Arpan-206 I mentioned this before, let's move this out to the tracks page ![]() |
||
<img alt={{@language.name}} src="{{@language.colorLogoUrl}}" ...attributes /> | ||
{{else if (eq @variant "gray")}} | ||
<img alt={{@language.name}} src="{{@language.grayLogoUrl}}" ...attributes /> | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
import { module, test } from 'qunit'; | ||
import { setupRenderingTest } from 'codecrafters-frontend/tests/helpers'; | ||
import { render, settled } from '@ember/test-helpers'; | ||
import { hbs } from 'ember-cli-htmlbars'; | ||
import { tracked } from '@glimmer/tracking'; | ||
|
||
// Mock Rive class for testing | ||
class MockRive { | ||
// Private properties | ||
_interval = null; | ||
_listeners = new Map(); | ||
|
||
// Public properties | ||
@tracked canvas; | ||
@tracked isLoaded = false; | ||
@tracked stateMachineNames = ['State Machine 1', 'State Machine 2']; | ||
@tracked lastPlayedStateMachine = null; | ||
@tracked lastInputsStateMachine = null; | ||
@tracked playCalls = 0; | ||
@tracked resetCalls = 0; | ||
|
||
// Constructor | ||
constructor(options) { | ||
this.canvas = options.canvas; | ||
} | ||
|
||
// Getter | ||
get interval() { | ||
return this._interval; | ||
} | ||
|
||
// Public methods | ||
off(event, callback) { | ||
const listeners = this._listeners.get(event); | ||
|
||
if (listeners) { | ||
const index = listeners.indexOf(callback); | ||
|
||
if (index > -1) { | ||
listeners.splice(index, 1); | ||
} | ||
} | ||
} | ||
|
||
on(event, callback) { | ||
if (!this._listeners.has(event)) { | ||
this._listeners.set(event, []); | ||
} | ||
|
||
this._listeners.get(event)?.push(callback); | ||
} | ||
|
||
play(stateMachineName) { | ||
this.playCalls++; | ||
this.lastPlayedStateMachine = stateMachineName; | ||
|
||
return stateMachineName; | ||
} | ||
|
||
reset() { | ||
this.resetCalls++; | ||
} | ||
|
||
simulateLoad() { | ||
this.isLoaded = true; | ||
this.triggerEvent('load'); | ||
} | ||
|
||
stateMachineInputs(stateMachineName) { | ||
this.lastInputsStateMachine = stateMachineName; | ||
|
||
return []; | ||
} | ||
|
||
stop() { | ||
// No-op for test | ||
} | ||
|
||
// Helper methods | ||
triggerEvent(event, ...args) { | ||
const listeners = this._listeners.get(event); | ||
|
||
if (listeners) { | ||
listeners.forEach((callback) => callback(event, ...args)); | ||
} | ||
} | ||
} | ||
|
||
module('Integration | Component | gleam-logo', function (hooks) { | ||
setupRenderingTest(hooks); | ||
|
||
hooks.beforeEach(function () { | ||
// Store original Rive constructor | ||
this.originalRive = window.Rive; | ||
// Replace with mock | ||
window.Rive = MockRive; | ||
}); | ||
|
||
hooks.afterEach(function () { | ||
// Restore original Rive constructor | ||
window.Rive = this.originalRive; | ||
}); | ||
|
||
module('Rendering', function () { | ||
test('it renders a canvas element', async function (assert) { | ||
await render(hbs`<GleamLogo />`); | ||
assert.dom('canvas').exists('Canvas element is rendered'); | ||
}); | ||
|
||
test('it initializes Rive with the canvas', async function (assert) { | ||
await render(hbs`<GleamLogo />`); | ||
|
||
const container = document.querySelector('.gleam-logo-container'); | ||
const canvas = container?.querySelector('canvas'); | ||
assert.ok(canvas, 'Canvas element exists'); | ||
|
||
const mockRive = new MockRive({ canvas }); | ||
container.__riveInstance = mockRive; | ||
|
||
mockRive.simulateLoad(); | ||
await settled(); | ||
|
||
assert.deepEqual(mockRive.stateMachineNames, ['State Machine 1', 'State Machine 2'], 'State machines are available after initialization'); | ||
}); | ||
}); | ||
|
||
module('Hover Behavior', function () { | ||
test('it triggers animation on hover', async function (assert) { | ||
await render(hbs`<GleamLogo />`); | ||
|
||
const container = document.querySelector('.gleam-logo-container'); | ||
const canvas = container?.querySelector('canvas'); | ||
assert.ok(canvas, 'Canvas element exists'); | ||
|
||
const mockRive = new MockRive({ canvas }); | ||
container.__riveInstance = mockRive; | ||
|
||
// Simulate the component's handleMouseEnter logic | ||
if (mockRive) { | ||
const stateMachines = mockRive.stateMachineNames; | ||
|
||
if (stateMachines && stateMachines.length > 0) { | ||
const stateMachineName = 'State Machine 1'; | ||
|
||
if (stateMachines.includes(stateMachineName)) { | ||
mockRive.reset(); | ||
mockRive.play(stateMachineName); | ||
} | ||
} | ||
} | ||
|
||
await settled(); | ||
|
||
assert.strictEqual(mockRive.playCalls, 1, 'Play was called once on hover'); | ||
assert.strictEqual(mockRive.resetCalls, 1, 'Reset was called once on hover'); | ||
}); | ||
}); | ||
|
||
module('Cleanup', function () { | ||
test('it cleans up resources on destroy', async function (assert) { | ||
await render(hbs`<GleamLogo />`); | ||
|
||
const container = document.querySelector('.gleam-logo-container'); | ||
const canvas = container?.querySelector('canvas'); | ||
assert.ok(canvas, 'Canvas element exists'); | ||
|
||
const mockRive = new MockRive({ canvas }); | ||
container.__riveInstance = mockRive; | ||
|
||
mockRive.stop = () => { | ||
assert.step('stop called'); | ||
}; | ||
|
||
// Trigger cleanup | ||
await render(hbs``); | ||
|
||
assert.verifySteps(['stop called'], 'Stop was called during cleanup'); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Arpan-206 Why is the naming convention inconsistent here?
did-insert
usesthis.handleDidInsert
(correct), andwill-destroy
usesthis.cleanupRive
instead ofthis.handleWillDestroy
?