Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 7 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ const path = require('path');
module.exports = buildEngine({
name,

// Lazy loading keeps the engine out of the host bundle, but it also keeps the
// engine's modules out of the dummy app that `ember test` builds, so every test
// importing `@fleetbase/fleetops-engine/*` fails to load. Eager loading is
// scoped to `ember test` run from this package, so host apps consuming the
// engine — including their own test builds — keep lazy loading. Addon index
// files are evaluated before ember-cli assigns EMBER_ENV, hence the argv check.
lazyLoading: {
enabled: true,
enabled: !(process.argv.includes('test') && process.cwd() === __dirname),
},

treeForLeaflet: function () {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"devDependencies": {
"@babel/eslint-parser": "^7.22.15",
"@babel/plugin-proposal-decorators": "^7.23.2",
"@ember/legacy-built-in-components": "^0.4.2",
"@ember/optional-features": "^2.0.0",
"@ember/test-helpers": "^3.2.0",
"@embroider/test-setup": "^3.0.2",
Expand Down
27 changes: 25 additions & 2 deletions pnpm-lock.yaml

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

7 changes: 1 addition & 6 deletions server/tests/CustomerContactIdentitySafetyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,10 @@
$apiContact = file_get_contents(__DIR__ . '/../src/Http/Controllers/Api/v1/ContactController.php');
$internalContact = file_get_contents(__DIR__ . '/../src/Http/Controllers/Internal/v1/ContactController.php');
$orderController = file_get_contents(__DIR__ . '/../src/Http/Controllers/Api/v1/OrderController.php');
$customerForm = file_get_contents(__DIR__ . '/../../addon/components/customer/form.hbs');

expect($apiContact)->toContain('$contactCandidate->assertCustomerIdentityIsAvailable();');
expect($internalContact)->toContain('$this->assertCustomerIdentityIsAvailable($input');
expect($orderController)->toContain('$customerCandidate->assertCustomerIdentityIsAvailable();');
expect($customerForm)->toContain('is_customer=true');
});

test('customer conflict audit command is registered and read only', function () {
Expand All @@ -59,7 +57,6 @@
$contactModel = file_get_contents(__DIR__ . '/../src/Models/Contact.php');
$customerCredentials = file_get_contents(__DIR__ . '/../src/Mail/CustomerCredentialsMail.php');
$customerEmail = file_get_contents(__DIR__ . '/../resources/views/mail/customer-credentials.blade.php');
$customerForm = file_get_contents(__DIR__ . '/../../addon/components/customer/form.hbs');

expect($internalContact)
->toContain('meta.customer_portal.send_welcome_email')
Expand All @@ -81,9 +78,7 @@
->and($customerEmail)->toContain('Your customer portal access is ready')
->and($customerEmail)->toContain('Sign in to customer portal')
->and($customerEmail)->toContain('Temporary password:')
->and($customerEmail)->toContain('You can change your password after signing in.')
->and($customerForm)->toContain('this.showWelcomeEmailOption')
->and($customerForm)->toContain('this.toggleWelcomeEmail');
->and($customerEmail)->toContain('You can change your password after signing in.');

expect($contactModel)
->toContain('assignUserToContactCompany')
Expand Down
86 changes: 0 additions & 86 deletions server/tests/TelematicsHardeningTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,20 +86,6 @@
->toContain('meta->provider_account_id');
});

test('device details render consistent telematics connection state and timestamp', function () {
$details = file_get_contents(__DIR__ . '/../../addon/components/device/details.hbs');
$header = file_get_contents(__DIR__ . '/../../addon/components/device/panel-header.hbs');

expect($details)
->toContain('format-date-fns this.lastSeenLabel "dd MMM yyyy, HH:mm"')
->not->toContain('format-date @resource.last_online_at');

expect($header)
->toContain('this.connectionStatus')
->toContain('this.lastOnlineAt')
->not->toContain('@resource.online "online"');
});

test('native providers normalize device payloads to canonical FleetOps keys', function () {
$providers = [
file_get_contents(__DIR__ . '/../src/Support/Telematics/Providers/AfaqyProvider.php'),
Expand Down Expand Up @@ -886,26 +872,6 @@ public function fetchDevicesForTest(): array
]);
})->skip('Requires isolated Laravel HTTP client fake state in the full application harness.');

test('telematics details use public id for consumer webhook URLs and do not read ember uuid', function () {
$component = file_get_contents(__DIR__ . '/../../addon/components/telematic/details.js');
$template = file_get_contents(__DIR__ . '/../../addon/components/telematic/details.hbs');

expect($component)
->toContain('const id = this.args.resource?.public_id;')
->toContain('return null;')
->not->toContain('this.args.resource?.uuid')
->not->toContain('this.args.resource?.public_id ?? this.args.resource?.id');

expect($template)
->toContain('this.hasWebhookUrl')
->toContain('Webhook URL unavailable until this integration has a public ID.')
->toContain('Provider polling');

expect($component)
->toContain('last_sync_job_id')
->toContain('last_sync_error');
});

test('native telematics providers expose local provider icons with a descriptor fallback', function () {
$config = include __DIR__ . '/../config/telematics.php';
$iconPath = '/engines-dist/images/telematics/providers/';
Expand Down Expand Up @@ -1315,7 +1281,6 @@ public function fetchDevicesForTest(array $credentials): array
test('telematics polling command is registered and scheduled for discovery providers by default', function () {
$command = file_get_contents(__DIR__ . '/../src/Console/Commands/SyncTelematics.php');
$provider = file_get_contents(__DIR__ . '/../src/Providers/FleetOpsServiceProvider.php');
$details = file_get_contents(__DIR__ . '/../../addon/components/telematic/details.hbs');

expect($command)
->toContain("protected \$signature = 'fleetops:sync-telematics")
Expand All @@ -1330,10 +1295,6 @@ public function fetchDevicesForTest(array $credentials): array
expect($provider)
->toContain('Console\\Commands\\SyncTelematics::class')
->toContain("command('fleetops:sync-telematics')->everyMinute()");

expect($details)
->toContain('Provider polling')
->toContain('Fleet-Ops polls this provider for device snapshots and telemetry updates.');
});

test('native endpoint fields are advanced optional overrides with provider defaults', function () {
Expand Down Expand Up @@ -1465,27 +1426,6 @@ public function fetchDevicesForTest(array $credentials): array
->not->toContain("'location',");
});

test('telematics setup renders endpoint overrides only in advanced settings', function () {
$formTemplate = file_get_contents(__DIR__ . '/../../addon/components/telematic/form.hbs');
$settingsTemplate = file_get_contents(__DIR__ . '/../../addon/components/telematic/settings.hbs');
$formComponent = file_get_contents(__DIR__ . '/../../addon/components/telematic/form.js');
$settingsComponent = file_get_contents(__DIR__ . '/../../addon/components/telematic/settings.js');

foreach ([$formComponent, $settingsComponent] as $component) {
expect($component)
->toContain('advancedCredentialFields')
->toContain('field.advanced || field.is_endpoint')
->toContain('!field.advanced && !field.is_endpoint');
}

foreach ([$formTemplate, $settingsTemplate] as $template) {
expect($template)
->toContain('Advanced connection settings')
->toContain('this.advancedCredentialFields')
->toContain('field.default_value');
}
});

test('telematics details logs are routed and use persisted safe data only', function () {
$routes = file_get_contents(__DIR__ . '/../src/routes.php');
$controller = file_get_contents(__DIR__ . '/../src/Http/Controllers/Internal/v1/TelematicController.php');
Expand All @@ -1510,32 +1450,6 @@ public function fetchDevicesForTest(array $credentials): array
->not->toContain('Log::');
});

test('telematics provider status display maps active and connected to connected', function () {
$indexController = file_get_contents(__DIR__ . '/../../addon/controllers/connectivity/telematics/index.js');
$detailsController = file_get_contents(__DIR__ . '/../../addon/controllers/connectivity/telematics/details.js');
$statusCell = file_get_contents(__DIR__ . '/../../addon/components/cell/telematic-status.js');

expect($indexController)
->toContain("cellComponent: 'cell/telematic-status'");

expect($detailsController)
->toContain("case 'active':\n return 'Connected';");

expect($statusCell)
->toContain("case 'active':")
->toContain("case 'connected':")
->toContain("return 'Connected';");
});

test('telematics overview attention items render as full width stacked alerts', function () {
$template = file_get_contents(__DIR__ . '/../../addon/components/telematic/details.hbs');

expect($template)
->toContain('<section class="flex flex-col gap-3">')
->toContain('class="w-full rounded-md border border-yellow-200 bg-yellow-50 p-3')
->not->toContain('lg:grid-cols-2');
});

test('device attachment morph types are normalized and legacy aliases are tolerated', function () {
$migration = file_get_contents(__DIR__ . '/../migrations/2026_06_15_000001_normalize_device_attachable_vehicle_morph_types.php');
$command = file_get_contents(__DIR__ . '/../src/Console/Commands/FixInvalidPolymorphicRelationTypeNamespaces.php');
Expand Down
100 changes: 87 additions & 13 deletions tests/integration/components/customer/form-test.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,100 @@
import Component from '@glimmer/component';
import Service from '@ember/service';
import { setComponentTemplate } from '@ember/component';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'dummy/tests/helpers';
import { render } from '@ember/test-helpers';
import { render, click } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

let modelSelectQueries;

class ModelSelectStub extends Component {
constructor() {
super(...arguments);
modelSelectQueries.push({ modelName: this.args.modelName, query: this.args.query });
}
}

class ExtensionManagerStub extends Service {
installed = [];

isInstalled(name) {
return this.installed.includes(name);
}

ensureEngineLoaded() {
return Promise.resolve();
}
}

function makeResource(initial = {}) {
return {
...initial,
set(key, value) {
this[key] = value;
},
setProperties(values) {
Object.assign(this, values);
},
};
}

module('Integration | Component | customer/form', function (hooks) {
setupRenderingTest(hooks);

test('it renders', async function (assert) {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.set('myAction', function(val) { ... });
hooks.beforeEach(function () {
modelSelectQueries = [];

this.owner.register('service:universe/extension-manager', ExtensionManagerStub);
this.owner.register('component:model-select', setComponentTemplate(hbs`<div data-test-model-select={{@modelName}}></div>`, ModelSelectStub));
});

test('the user account selector only offers customer users without a contact', async function (assert) {
this.set('customer', makeResource({ isNew: true }));

await render(hbs`<Customer::Form @resource={{this.customer}} />`);

const userSelect = modelSelectQueries.find((entry) => entry.modelName === 'user');

assert.deepEqual(userSelect.query, { doesnt_have_contact: true, is_customer: true });
});

test('the welcome email option is offered when creating a customer with the portal installed', async function (assert) {
this.owner.lookup('service:universe/extension-manager').installed = ['@fleetbase/customer-portal-engine'];
this.set('customer', makeResource({ isNew: true }));

await render(hbs`<Customer::Form @resource={{this.customer}} />`);

assert.dom('input[type="checkbox"]').exists({ count: 1 });
assert.dom('input[type="checkbox"]').isNotChecked('the welcome email is opt in');
assert.dom().includesText('Send customer portal welcome email');
});

test('opting in stores the welcome email flag on the customer portal meta', async function (assert) {
this.owner.lookup('service:universe/extension-manager').installed = ['@fleetbase/customer-portal-engine'];
this.set('customer', makeResource({ isNew: true }));

await render(hbs`<Customer::Form />`);
await render(hbs`<Customer::Form @resource={{this.customer}} />`);
await click('input[type="checkbox"]');

assert.deepEqual(this.customer.meta, { customer_portal: { send_welcome_email: true } });
});

test('the welcome email option is hidden without the customer portal extension', async function (assert) {
this.set('customer', makeResource({ isNew: true }));

await render(hbs`<Customer::Form @resource={{this.customer}} />`);

assert.dom('input[type="checkbox"]').doesNotExist();
assert.dom().doesNotIncludeText('Send customer portal welcome email');
});

assert.dom().hasText('');
test('the welcome email option is hidden when editing an existing customer', async function (assert) {
this.owner.lookup('service:universe/extension-manager').installed = ['@fleetbase/customer-portal-engine'];
this.set('customer', makeResource({ isNew: false }));

// Template block usage:
await render(hbs`
<Customer::Form>
template block text
</Customer::Form>
`);
await render(hbs`<Customer::Form @resource={{this.customer}} />`);

assert.dom().hasText('template block text');
assert.dom('input[type="checkbox"]').doesNotExist();
});
});
25 changes: 25 additions & 0 deletions tests/integration/components/device/details-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,29 @@ module('Integration | Component | device/details', function (hooks) {
assert.dom().includesText('Geotab');
assert.dom().includesText('Online');
});

test('it renders the last seen timestamp in the shared telematics format', async function (assert) {
this.set('device', {
displayName: 'Gateway 101',
connection_status: 'online',
device_id: 'VG-101',
last_online_at: new Date(2026, 5, 18, 15, 28),
});

await render(hbs`<Device::Details @resource={{this.device}} />`);

assert.dom().includesText('Last seen: 18 Jun 2026, 15:28');
});

test('it renders a placeholder when the device has never reported', async function (assert) {
this.set('device', {
displayName: 'Gateway 101',
connection_status: 'never_connected',
device_id: 'VG-101',
});

await render(hbs`<Device::Details @resource={{this.device}} />`);

assert.dom().includesText('Last seen: -');
});
});
Loading
Loading