Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,281 @@
/* global AppWindow, Card, MocksHelper, MockPromise, Icon,
TaskManagerUtils, SwipeToKillMotion */
'use strict';
require('/shared/js/sanitizer.js');
requireApp('system/test/unit/mock_app_window.js');
require('/shared/test/unit/mocks/mock_promise.js');
require('/shared/js/homescreens/icon.js');
var mocksForCard = new MocksHelper([
'AppWindow'
]).init();
suite('system/Card', function() {
function makeApp(config) {
var appWindow = new AppWindow({
launchTime: 4,
title: config.title,
name: config.name || 'dummyapp',
manifest: {
orientation: config.orientation || 'portrait-primary'
},
isPrivate: config.isPrivate,
rotatingDegree: config.rotatingDegree || 0,
getScreenshot: function(callback) {
callback();
},
origin: config.origin || 'app://' +
(config.name || 'dummyapp') + '.gaiamobile.org',
url: config.url,
blur: function() {}
});
appWindow.browser.element.src = appWindow.origin + '/index.html';
if (config.isBrowser) {
appWindow.isBrowser = () => config.isBrowser;
}
return appWindow;
}
mocksForCard.attachTestHelpers();
var mockManager;
var cardsList;
var iconDataURI = 'data:image/png;base64,abc+';
var getIconPromise;
suiteSetup(function(done) {
cardsList = document.createElement('ul');
cardsList.id = 'cards-list';
document.body.appendChild(cardsList);
requireApp('system/js/service.js');
requireApp('system/js/base_ui.js');
requireApp('system/js/task_manager_utils.js');
requireApp('system/js/card.js', done);
});
setup(function() {
getIconPromise = new MockPromise();
this.sinon.stub(AppWindow.prototype, 'getSiteIconUrl')
.returns(getIconPromise);
mockManager = {
SWIPE_UP_THRESHOLD: 480/4,
cardsList: cardsList
};
this.sinon.stub(Icon.prototype, 'renderBlob', function() {
this.elem.style.backgroundImage = 'url(' + this.uri + ')';
});
});
teardown(function() {
cardsList.innerHTML = '';
if (getIconPromise.then.called) {
getIconPromise.mFulfillToValue({originalUrl: iconDataURI, blob: {}});
}
});
suite('render > ', function() {
test('does nothing when stayInvisible is set', function() {
var card = new Card(makeApp({
name: 'anyapp',
title: 'sometitle'
}), { stayInvisible: true });
assert.equal(card.element.style.display, 'none');
assert.equal(card.title, undefined);
});
test('adds browser class for browser windows', function(){
var card = new Card(makeApp({
name: 'browserapp',
isBrowser: true,
title: 'Page title'
}));
assert.ok(card.element.classList.contains('browser'),
'has browser class');
assert.equal(
card.element.querySelector('.title').textContent,
card.app.title);
assert.ok(!card.element.classList.contains('.private'));
});
test('adds private class for private windows', function(){
var card = new Card(makeApp({
name: 'privatewindow',
isBrowser: true,
isPrivate: true
}));
assert.ok(card.element.classList.contains('private'));
});
test('unkillable card', function(){
var app = makeApp({
name: 'privatewindow',
isBrowser: true,
isPrivate: true
});
app.attentionWindow = true; // makes the card unkillable
var card = new Card(app);
assert.equal(
card.element.querySelector('.close-button').style.visibility,
'hidden');
});
test('app security for browser windows', function() {
var app = makeApp({
name: 'browserwindow',
isBrowser: true
});
app.getSSLState = () => 'broken';
var card = new Card(app);
assert.equal(card.element.dataset.ssl, 'broken');
});
test('browser windows display URL in their subTitle', function() {
var app = makeApp({
name: 'browserwindow',
isBrowser: true
});
app.config.url = 'https://someorigin.org/foo';
var card = new Card(app);
this.sinon.stub(TaskManagerUtils, 'getDisplayUrlForApp')
.returns('someorigin.org/foo');
assert.ok(card.element.classList.contains('show-subtitle'));
assert.equal(
card.element.querySelector('.subtitle-url').textContent,
'someorigin.org/foo');
});
});
suite('loadIcon > ', function() {
test('calls loadAppIcon', function(){
this.sinon.stub(TaskManagerUtils, 'loadAppIcon')
.returns(Promise.resolve());
var card = new Card(makeApp({ name: 'app' }));
card.loadIcon();
assert.ok(TaskManagerUtils.loadAppIcon.called);
assert.equal(TaskManagerUtils.loadAppIcon.firstCall.args[0], card.app);
});
});
});
suite('system/SwipeToKillMotion', function() {
var motion, el, setTranslateY;
var currentY;
setup(function() {
el = document.createElement('div');
setTranslateY = this.sinon.stub();
currentY = 0;
motion = new SwipeToKillMotion(el, {
setTranslateY(newY) {
currentY = newY;
setTranslateY(currentY);
}
});
});
teardown(function() {
});
function touch(subtype, x, y) {
var evt = new CustomEvent('touch' + subtype);
evt.touches = evt.targetTouches = [];
if (arguments.length > 1) {
evt.touches.push({
clientX: x, clientY: y
});
}
return evt;
}
test('swipe up to kill', function() {
var dropStub = this.sinon.stub();
var willDragStub = this.sinon.stub();
el.addEventListener('card-dropped', dropStub);
el.addEventListener('card-will-drag', willDragStub);
motion.el.dispatchEvent(touch('start', 0, 0));
motion.el.dispatchEvent(touch('move', 0, -10));
assert.isTrue(motion.activelyDragging);
assert.equal(currentY, '-10px');
motion.el.dispatchEvent(touch('move', 0, -20));
motion.el.dispatchEvent(touch('move', 0, -30));
motion.el.dispatchEvent(touch('move', 0, -40));
motion.el.dispatchEvent(touch('end'));
assert.isFalse(motion.activelyDragging);
assert.equal(currentY, '-200%');
assert.isTrue(dropStub.calledOnce);
assert.isTrue(dropStub.firstCall.args[0].detail.willKill);
assert.isTrue(willDragStub.calledOnce);
});
test('swipe up but without enough velocity', function() {
var dropStub = this.sinon.stub();
el.addEventListener('card-dropped', dropStub);
motion.el.dispatchEvent(touch('start', 0, 0));
motion.el.dispatchEvent(touch('move', 0, -10));
motion.el.dispatchEvent(touch('move', 0, -20));
motion.el.dispatchEvent(touch('move', 0, -20));
motion.el.dispatchEvent(touch('move', 0, -10));
motion.el.dispatchEvent(touch('end'));
assert.equal(currentY, '0px');
assert.isTrue(dropStub.calledOnce);
assert.isFalse(dropStub.firstCall.args[0].detail.willKill);
});
test('touch events are intercepted by the card', function() {
var touchEvent = touch('start', 0, 0);
this.sinon.spy(touchEvent, 'stopPropagation');
motion.el.dispatchEvent(touchEvent);
assert.isTrue(touchEvent.stopPropagation.called);
});
test('swipe up, but event canceled', function() {
var dropStub = this.sinon.stub();
el.addEventListener('card-will-drag', (evt) => {
evt.preventDefault();
});
motion.el.dispatchEvent(touch('start', 0, 0));
motion.el.dispatchEvent(touch('move', 0, -10));
assert.equal(currentY, '0px');
motion.el.dispatchEvent(touch('move', 0, -20));
motion.el.dispatchEvent(touch('move', 0, -30));
motion.el.dispatchEvent(touch('move', 0, -40));
motion.el.dispatchEvent(touch('end'));
assert.equal(currentY, '0px');
assert.isFalse(dropStub.calledOnce); // no drop event expected
});
test('click while dragging should be ignored', function() {
motion.el.dispatchEvent(touch('start', 0, 0));
motion.el.dispatchEvent(touch('move', 0, -10));
motion.el.dispatchEvent(touch('move', 0, -20));
motion.el.dispatchEvent(touch('move', 0, -30));
var click = new CustomEvent('click', { cancelable: true });
this.sinon.spy(click, 'stopPropagation');
motion.el.dispatchEvent(click);
assert.isTrue(click.defaultPrevented);
assert.isTrue(click.stopPropagation.called);
});
});