UNPKG

@google/model-viewer

Version:

Easily display interactive 3D models on the web and in AR!

255 lines 13.4 kB
/* @license * Copyright 2019 Google LLC. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an 'AS IS' BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { $defaultPosterElement, $posterContainerElement, LoadingMixin, POSTER_TRANSITION_TIME } from '../../features/loading.js'; import ModelViewerElementBase, { $userInputElement } from '../../model-viewer-base.js'; import { CachingGLTFLoader } from '../../three-components/CachingGLTFLoader.js'; import { timePasses, waitForEvent } from '../../utilities.js'; import { assetPath, dispatchSyntheticEvent, pickShadowDescendant, until } from '../helpers.js'; import { BasicSpecTemplate } from '../templates.js'; const expect = chai.expect; const CUBE_GLB_PATH = assetPath('models/cube.gltf'); const HORSE_GLB_PATH = assetPath('models/Horse.glb'); suite('ModelViewerElementBase with LoadingMixin', () => { suite('when registered', () => { let nextId = 0; let tagName; let ModelViewerElement; setup(() => { tagName = `model-viewer-loading-${nextId++}`; ModelViewerElement = class extends LoadingMixin(ModelViewerElementBase) { static get is() { return tagName; } }; customElements.define(tagName, ModelViewerElement); }); BasicSpecTemplate(() => ModelViewerElement, () => tagName); // TODO: Elements must have loaded to hide poster... suite('loading', () => { let element; setup(async () => { element = new ModelViewerElement(); document.body.insertBefore(element, document.body.firstChild); element.poster = assetPath('../screenshot.png'); // Wait at least a microtask for size calculations await timePasses(); }); teardown(() => { CachingGLTFLoader.clearCache(); if (element.parentNode != null) { element.parentNode.removeChild(element); } }); test('creates a poster element that captures interactions', async () => { const picked = pickShadowDescendant(element); expect(picked).to.be.ok; // TODO(cdata): Leaky internal details here: expect(picked.id).to.be.equal('default-poster'); }); test('does not load when hidden from render tree', async () => { let loadDispatched = false; let preloadDispatched = false; const loadHandler = () => { loadDispatched = true; }; const preloadHandler = () => { preloadDispatched = true; }; element.addEventListener('load', loadHandler); element.addEventListener('preload', preloadHandler); element.style.display = 'none'; // Give IntersectionObserver a chance to notify. In Chrome, this takes // two rAFs (empirically observed). Await extra time just in case: await timePasses(100); element.src = CUBE_GLB_PATH; await timePasses(500); // Arbitrary time to allow model to load element.removeEventListener('load', loadHandler); element.removeEventListener('preload', preloadHandler); expect(loadDispatched).to.be.false; expect(preloadDispatched).to.be.false; }); suite('load', () => { suite('when a model src changes after loading', () => { setup(async () => { element.src = CUBE_GLB_PATH; await waitForEvent(element, 'load'); }); test('only dispatches load once per src change', async () => { let loadCount = 0; const onLoad = () => { loadCount++; }; element.addEventListener('load', onLoad); try { element.src = HORSE_GLB_PATH; await waitForEvent(element, 'load'); element.src = CUBE_GLB_PATH; await waitForEvent(element, 'load'); // Give any late-dispatching events a chance to dispatch await timePasses(300); expect(loadCount).to.be.equal(2); } finally { element.removeEventListener('load', onLoad); } }); test('getDimensions() returns correct size', () => { const size = element.getDimensions(); expect(size.x).to.be.eq(1); expect(size.y).to.be.eq(1); expect(size.z).to.be.eq(1); }); }); }); suite('loading', () => { suite('src changes quickly', () => { test('eventually notifies that current src is preloaded', async () => { element.loading = 'eager'; element.src = CUBE_GLB_PATH; await timePasses(); element.src = HORSE_GLB_PATH; let preloadEvent = null; const onPreload = (event) => { if (event.detail.url === HORSE_GLB_PATH) { preloadEvent = event; } }; element.addEventListener('preload', onPreload); await until(() => element.loaded); await timePasses(); element.removeEventListener('preload', onPreload); expect(preloadEvent).to.be.ok; }); }); suite('reveal', () => { suite('auto', () => { test('hides poster when element loads', async () => { element.loading = 'eager'; element.src = CUBE_GLB_PATH; await waitForEvent(element, 'model-visibility', (event) => event.detail.visible); const input = element[$userInputElement]; const picked = pickShadowDescendant(element); expect(picked).to.be.equal(input); }); }); suite('interaction', () => { test('retains poster after loading', async () => { element.loading = 'eager'; element.reveal = 'interaction'; element.src = CUBE_GLB_PATH; await waitForEvent(element, 'load'); await timePasses(POSTER_TRANSITION_TIME + 100); const input = element[$userInputElement]; const picked = pickShadowDescendant(element); expect(picked).to.not.be.equal(input); }); suite('when focused', () => { test('can hide the poster with keyboard interaction', async () => { element.loading = 'eager'; element.reveal = 'interaction'; element.src = CUBE_GLB_PATH; const posterElement = element[$defaultPosterElement]; const inputElement = element[$userInputElement]; await waitForEvent(element, 'load'); // NOTE(cdata): Currently, Firefox does not forward focus // when delegatesFocus is true but focus is triggered // manually (e.g., with the .focus() method). posterElement.focus(); expect(element.shadowRoot.activeElement) .to.be.equal(posterElement); dispatchSyntheticEvent(posterElement, 'keydown', { keyCode: 13 }); await until(() => { return element.shadowRoot.activeElement === inputElement; }); }); }); }); suite('manual', () => { test('does not hide poster until dismissed', async () => { element.loading = 'eager'; element.reveal = 'manual'; element.src = CUBE_GLB_PATH; const posterElement = element[$defaultPosterElement]; const input = element[$userInputElement]; await waitForEvent(element, 'load'); posterElement.focus(); expect(element.shadowRoot.activeElement) .to.be.equal(posterElement); element.dismissPoster(); await until(() => { return element.shadowRoot.activeElement === input; }); }); }); }); }); suite('configuring poster via attribute', () => { suite('removing the attribute', () => { test('sets poster to null', async () => { // NOTE(cdata): This is less important after we resolve // https://github.com/PolymerLabs/model-viewer/issues/76 element.setAttribute('poster', CUBE_GLB_PATH); await timePasses(); element.removeAttribute('poster'); await timePasses(); expect(element.poster).to.be.equal(null); }); }); }); suite('with loaded model src', () => { setup(() => { element.src = CUBE_GLB_PATH; }); test('can be hidden imperatively', async () => { const ostensiblyThePoster = pickShadowDescendant(element); element.dismissPoster(); await waitForEvent(element, 'model-visibility', event => event.detail.visible === true); const ostensiblyNotThePoster = pickShadowDescendant(element); expect(ostensiblyThePoster).to.not.be.equal(ostensiblyNotThePoster); }); suite('when poster is hidden', () => { setup(async () => { element.dismissPoster(); await waitForEvent(element, 'model-visibility', event => event.detail.visible === true); }); test('allows the input to be interactive', async () => { const input = element[$userInputElement]; const picked = pickShadowDescendant(element); expect(picked).to.be.equal(input); }); test('when src is reset, poster is dismissable', async () => { const posterElement = element[$defaultPosterElement]; const posterContainer = element[$posterContainerElement]; const inputElement = element[$userInputElement]; element.reveal = 'interaction'; element.src = null; await timePasses(); element.src = CUBE_GLB_PATH; await timePasses(); expect(posterContainer.classList.contains('show')).to.be.true; posterElement.focus(); expect(element.shadowRoot.activeElement) .to.be.equal(posterElement); dispatchSyntheticEvent(posterElement, 'keydown', { keyCode: 13 }); await until(() => { return element.shadowRoot.activeElement === inputElement; }); }); }); }); }); }); }); //# sourceMappingURL=loading-spec.js.map