Skip to content

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

Merged
merged 45 commits into from
May 27, 2025
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
52b2af2
Animation on hover w/ Gleam Logo
Arpan-206 Apr 20, 2025
6859dfc
linting stuff
Arpan-206 Apr 20, 2025
175ee69
manually making the linter happy
Arpan-206 Apr 20, 2025
c864859
Removing debugging stuff
Arpan-206 Apr 20, 2025
c4ab10f
Working on hover too
Arpan-206 Apr 20, 2025
ea04fe0
linter wasn't feeling too happy ig
Arpan-206 Apr 20, 2025
a20cf9f
remove logs and stuff
Arpan-206 Apr 20, 2025
6cc3d5b
Add integration test
Arpan-206 Apr 20, 2025
0335cf1
linters will be the end of me
Arpan-206 Apr 20, 2025
72c9864
pretty much the last msg
Arpan-206 Apr 20, 2025
4ecb22e
quality fix?
Arpan-206 Apr 21, 2025
0ac1be3
linter
Arpan-206 Apr 21, 2025
cf62de1
tests ain't running
Arpan-206 Apr 21, 2025
1d67c55
Fixed most stuff except maybe the moseevents
Arpan-206 Apr 21, 2025
d007423
used SMs and stuff
Arpan-206 Apr 23, 2025
88fa7b8
committing if we ever need to debug animations
Arpan-206 Apr 30, 2025
257025c
Remove all the manual animation bits, fully automatic now
Arpan-206 Apr 30, 2025
e951505
Fix tests
Arpan-206 Apr 30, 2025
d798652
linter needs to be happy
Arpan-206 Apr 30, 2025
8052924
[percy] add sentry logging
Arpan-206 Apr 30, 2025
8603f7e
Changes incorporation w/o breaks
Arpan-206 Apr 30, 2025
5853d88
Linter was sad
Arpan-206 Apr 30, 2025
53f6401
It resizes dynamically
Arpan-206 May 7, 2025
711a877
Doing anything to make this work best atp
Arpan-206 May 7, 2025
733209d
Updated the animation file
Arpan-206 May 8, 2025
63cdade
move gleam logo thingy to tracks page + dynamic sizing
Arpan-206 May 8, 2025
f6bfef5
Renamed stuff to be more generic and not just gleam specific + demo
Arpan-206 May 9, 2025
a9d58af
linter worship go brrrr
Arpan-206 May 9, 2025
662128f
Add some more demos + fix tests
Arpan-206 May 9, 2025
1ea7dda
minor bunny fix
Arpan-206 May 9, 2025
2b86774
should fix the resize issues
Arpan-206 May 9, 2025
ad8dbb2
linter doin linting
Arpan-206 May 9, 2025
75d66f3
use shorthands
Arpan-206 May 9, 2025
9c91bd9
🤞
Arpan-206 May 11, 2025
bac2acc
[percy] this needed too
Arpan-206 May 11, 2025
8e9e0d0
tests are acting weird
Arpan-206 May 11, 2025
d8cfe02
Fix the non responsiveness
May 24, 2025
cfe0f6a
turns out we could use native html + tailwind for sizes
May 24, 2025
bcf6a32
SImple change to prvent horizontal stretch
May 24, 2025
df4458e
Remove not needed code
May 26, 2025
88b1138
Improve testing
May 26, 2025
dd80705
Update Animation File
May 27, 2025
092fcd5
Update to use WaitUntil
May 27, 2025
3757657
tests weren't running
May 27, 2025
6ad0f70
remove timeout
May 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/components/gleam-logo.hbs
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>
Copy link
Member

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 uses this.handleDidInsert (correct), and will-destroy uses this.cleanupRive instead of this.handleWillDestroy?

{{! The canvas will be inserted here by Rive }}
</div>
108 changes: 108 additions & 0 deletions app/components/gleam-logo.ts
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;
Copy link
Member

Choose a reason for hiding this comment

The 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Add 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:

  1. Component rendering
  2. Animation initialization and event handling
  3. Cleanup processes

Would you like me to provide a test implementation?


🌐 Web query:

How to write unit tests for Ember components with Rive animations?

💡 Result:

Writing Unit Tests for Ember Components with Rive Animations

Overview

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 <canvas> element (or similar), and their side effects aren't always visible in the DOM as text or attributes, you must use a mix of rendering tests and DOM assertions, and potentially mock or spy on Rive's APIs.


Recommended Approach

1. Use Rendering or Integration Tests

While "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].

  • Setup using setupRenderingTest(hooks) in your test module.
  • Use the render helper to instantiate your component in the test environment.
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 Logic

External libraries like Rive often require DOM interaction and asynchronous file loading. To keep your tests fast and reliable:

  • Stub or Mock Rive: Mock the global Rive constructor or inject a mock via Ember's dependency injection. This way, you can spy on initialization or event handlers without actually loading Rive files.

    • For example, if your component instantiates new Rive({ ... }), mock this in your test so you can assert it was called with the expected props.
// In your test setup
let riveInstanceMock = {
  on: sinon.spy(),
  play: sinon.spy(),
  pause: sinon.spy()
};

window.Rive = sinon.stub().returns(riveInstanceMock);
  • Assert event handlers: If your component listens for Rive events (like on(EventType.RiveEvent, handler)), you can simulate these events in your test by calling the handler directly and asserting component behavior[6].

3. Assert DOM and Behavioral Effects

  • Check that the correct canvas or container is rendered.
  • If your UI reacts to Rive events (for example, on animation completion), simulate those events and verify the correct DOM changes or actions are triggered.
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 Behavior

Many Rive interactions (loading, animation events) are async. Use async/await in your test functions and await any helpers or simulated event triggers[7].


5. Tips and Good Practices

  • Only assert what your component is responsible for (e.g., the canvas is rendered, specific event handlers are registered, or the component reacts to Rive events).
  • Avoid dependence on actual Rive files or network requests.
  • Use Ember’s pauseTest() helper if you need to manually inspect DOM output during test development[7].

Example Workflow

Purpose Test Method
Canvas is rendered Assert with assert.dom('canvas').exists()
Rive is initialized Mock Rive and assert initialization parameters
Animation event fires Simulate event, assert component reaction
UI updates after event Assert DOM updates or action calls

Summary

  • Use rendering (integration) tests for Ember components with Rive.
  • Mock Rive APIs to avoid real animation/network dependencies.
  • Simulate Rive events and assert component reactions.
  • Focus assertions on rendered elements and component behavior, not Rive’s internal rendering[1][5][6][7].

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 GleamLogoComponent currently has no test coverage. To ensure its Rive‑based animation logic works as intended, add an integration (rendering) test that:

  • Sets up the rendering test environment (setupRenderingTest(hooks))
  • Renders the component via await render(hbs)
  • Asserts that a <canvas> (or designated container) is inserted into the DOM
  • Stubs or mocks the global Rive constructor:
    • e.g.
      let riveMock = { on: sinon.spy(), play: sinon.spy(), pause: sinon.spy() };
      window.Rive = sinon.stub().returns(riveMock);
  • Verifies new Rive({...}) is invoked with the expected init options (canvas element, autoplay, etc.)
  • Simulates a Rive event via riveMock.on.callArgWith(1, { data: { name: '…' } }) and asserts the component updates accordingly (DOM changes or tracked‐property updates)
  • Confirms cleanup: after destroying the component, ensure any Rive playback was stopped or the instance reference cleared

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;

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%';
canvas.style.height = '100%';
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);
Copy link
Contributor

Choose a reason for hiding this comment

The 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setTimeout(() => {
if (this.riveInstance) {
this.riveInstance.play('State Machine 3');
}
}, 1000);
// app/components/gleam-logo.ts
export default class GleamLogoComponent extends Component {
@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;
}
// …any other teardown logic…
}
private initializeRive() {
new Rive({
// …other options…
onLoad: () => {
// Play State Machine 3 after 1 second to reset
this.resetTimeout = window.setTimeout(() => {
if (this.riveInstance) {
this.riveInstance.play('State Machine 3');
}
}, 1000);
}
});
}
}


// 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');
}
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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');
+  }

Committable suggestion skipped: line range outside the PR's diff.

}
},
});
} catch (error: unknown) {
console.error('Error setting up Rive:', error);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's report this to sentry, don't just swallow and ignore

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Implement 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:

Does Ember have a built-in error reporting service or recommended error handling pattern for components?

💡 Result:

Ember Error Reporting and Error Handling Patterns

Ember 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 Ember.onerror hook. This function catches uncaught errors thrown anywhere within your Ember application, allowing you to implement custom logic for logging, user notification, or reporting to external services. For promise-related errors, Ember exposes the Ember.RSVP.on('error', handler) hook, which allows you to catch unhandled promise rejections globally[2][6].

Recommended Error Handling Patterns

  • Ember.onerror: Define a global error handler to catch and process uncaught exceptions. This can be used for logging and reporting errors.
  • RSVP Promise Errors: Use Ember.RSVP.configure('onerror', handler) to capture errors from unhandled promise rejections[2][6].
  • Route-Specific Errors: Handle errors in route actions such as the error action within a route, which will be triggered when errors occur during transitions.
  • Try-Catch Blocks: Use try-catch blocks in your methods or actions for more granular error handling, especially in asynchronous code[6].
  • Display User-Friendly Messages: Normalize errors and display clear, actionable error messages to users.

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:

  • Sentry: Official Sentry SDK for Ember (@sentry/ember) automatically captures uncaught errors and exceptions. After installing the addon, you initialize Sentry early in your application’s startup file, and errors will be reported automatically[3][8]. You can also manually report errors using Sentry.captureException() within your code[4].
  • Other Services: Platforms like Rollbar, Errorception, and ExceptionHub are also compatible with Ember via global hooks[2].

Community Addons

There are several Ember addons to enhance error handling and reporting. For example, ember-error-handler centralizes error capturing and lets you route errors to logging services, custom error pages, or whatever logic you need[1][9].

Summary Table: Ember Error Handling Approaches

Pattern/Tool Purpose Built-in or Addon Usage
Ember.onerror Catch uncaught errors app-wide Built-in Global handler for logging/reporting
RSVP.on('error') Handle uncaught promise rejections Built-in Promise rejection handling
Route error action Catch errors in specific route contexts Built-in Show route-level error UI
Sentry/Rollbar Report errors to external services Third-party addon Error monitoring and alerting
ember-error-handler Centralize and customize error handling Community addon Custom logic, UI, or remote logging

Conclusion

Ember does not have an all-in-one built-in error reporting service, but it provides robust primitives (Ember.onerror, RSVP hooks, and route error actions) for handling and customizing error behavior. For error reporting, the Ember ecosystem supports seamless integration with tools like Sentry via official and community-maintained addons, and there are established patterns for routing errors and providing user feedback in components and across the app[2][6][8][9].

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:
– app/components/gleam-logo.ts (lines 96–98)

• 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:

  1. Add @tracked hasLoadError = false; in the component.
  2. Update the template to render a static logo (or message) when hasLoadError is true.
  3. Initialise and configure your chosen monitoring addon (e.g. @sentry/ember) and/or implement an Ember.onerror hook in app/app.js for centralized reporting.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (error: unknown) {
console.error('Error setting up Rive:', error);
}
} catch (error: unknown) {
// 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;
}

}
}

declare module '@glint/environment-ember-loose/registry' {
export default interface Registry {
GleamLogo: typeof GleamLogoComponent;
}
}
4 changes: 3 additions & 1 deletion app/components/language-logo.hbs
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")}}
Copy link
Member

Choose a reason for hiding this comment

The 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

Screenshot 2025-04-30 at 12 29 06

<img alt={{@language.name}} src="{{@language.colorLogoUrl}}" ...attributes />
{{else if (eq @variant "gray")}}
<img alt={{@language.name}} src="{{@language.grayLogoUrl}}" ...attributes />
Expand Down
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@
},
"dependencies": {
"@rails/actioncable": "^8.0.200",
"@rive-app/canvas": "^2.27.0",
"@stripe/stripe-js": "^5.5.0",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/typography": "^0.5.16",
Expand Down
Binary file not shown.
180 changes: 180 additions & 0 deletions tests/integration/components/gleam-logo-test.js
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');
});
});
});
Loading