import { A } from '@ember/array';
import ArrayProxy from '@ember/array/proxy';
import { assign } from '@ember/polyfills';
import Route from '@ember/routing/route';
import RSVP from 'rsvp';
import { run } from '@ember/runloop';
import EmberObject, { get } from '@ember/object';
import { module, test } from 'qunit';
import InfinityModel from 'ember-infinity/lib/infinity-model';
import { setupTest } from 'ember-qunit';
module('Unit | RouteMixin', function(hooks) {
setupTest(hooks);
hooks.beforeEach(function() {
this.EA = (content, meta={}) => {
return ArrayProxy.create(assign({ content: A(content) }, meta));
};
// create mock store first
this.createMockStore = (resolution, assertion) => {
return {
query() {
if (assertion) {
assertion.apply(this, arguments);
}
return RSVP.resolve(resolution);
}
};
}
// then create the route with the infinity service patched store
this.createRoute = (infinityModelArgs = [null], store = null) => {
if (store) {
infinityModelArgs[1] = assign({}, infinityModelArgs[1], store);
}
store = store || { store: this.createMockStore() };
this.infinity = this.owner.factoryFor('service:infinity').create(store);
let RouteObject = Route.extend({
infinity: this.infinity,
model() {
return this.infinity.model(...infinityModelArgs);
}
});
return RouteObject.create();
}
this.callModelHook = (route) => {
let model;
run(() => {
route.model().then(result => {
model = result;
});
});
route.set('controller', EmberObject.create({ model }));
return model;
}
});
module('basics', function() {
test('it works', function(assert) {
let RouteObject = Route.extend();
let route = RouteObject.create();
assert.ok(route);
});
test('it can not use infinityModel that is not an instance of InfinityModel', function (assert) {
assert.expect(1);
const ExtendedEmberObject = EmberObject.extend({
customId: 2,
buildParams() {
let params = this._super(...arguments);
params['custom_id'] = get(this, 'customId');
return params;
}
});
let item = { id: 1, title: 'The Great Gatsby' };
let route = this.createRoute(['post', { store: this.createMockStore(this.EA([item])) }, ExtendedEmberObject]);
try {
route.model();
} catch(e) {
assert.equal(e.message, 'Ember Infinity: You must pass an Infinity Model instance as the third argument', "wat");
}
});
test('it can use infinityModel with a custom data store', function(assert) {
let item = this.EA({ id: 1, title: 'The Great Gatsby' });
let mockStore = {
query() {
return RSVP.resolve(item);
}
};
let route = this.createRoute(['post'], {
store: mockStore
});
try {
route.model();
assert.ok(route.get('infinity.store').query, 'custom store works');
} catch(e) {
assert.ok(false, 'something failed');
}
});
test('custom data store can specify custom query method', function(assert) {
let EA = this.EA;
let mockStore =
{
findAll() {
let item = { id: 1, title: 'The Great Gatsby' };
return RSVP.resolve(EA([item]));
}
};
let route = this.createRoute(['post'], { store: mockStore, storeFindMethod: 'findAll' });
try {
route.model();
assert.ok(true, 'custom store with specified query method works');
} catch(e) {
assert.ok(false, 'something failed');
}
});
test('custom data store must specify custom query method', function(assert) {
let simpleStore = {
findAll() {
return RSVP.resolve();
}
};
let route = this.createRoute(['post'], { store: simpleStore });
try {
route.model();
} catch(e) {
assert.equal(e.message, 'Ember Infinity: Custom data store must specify query method');
}
});
test('it can not use infinityModel without passing a string for custom data store', function(assert) {
let route = this.createRoute(['post'], { store: 234 });
try {
route.model();
} catch(e) {
assert.equal(e.message, 'Ember Infinity: Custom data store must specify query method');
}
});
test('it can not use infinityModel without a Model Name', function(assert) {
let route = this.createRoute();
try {
route.model();
} catch(e) {
assert.equal(e.message, 'Ember Infinity: You must pass a Model Name to infinityModel');
}
});
test('it sets state before it reaches the end', function(assert) {
let store = this.createMockStore(this.EA([{id: 1, name: 'Test'}], { meta: { total_pages: 31 } } ));
let route = this.createRoute(['item'], { store });
let model = this.callModelHook(route);
assert.equal(model.get('_totalPages'), 31, '_totalPages');
assert.equal(model.get('currentPage'), 1, 'currentPage');
assert.equal(model.get('canLoadMore'), true, 'canLoadMore');
assert.notOk(route.get('_extraParams'), 'extra params are empty');
assert.ok(!model.get('reachedInfinity'), 'Should not reach infinity');
});
test('it sets count state before it reaches the end', function(assert) {
let store = this.createMockStore(this.EA([{id: 1, name: 'Test'}], { meta: { count: 31 } } ));
let route = this.createRoute(['item'], { store });
let model = this.callModelHook(route);
assert.equal(model.get('_count'), 31, '_count');
assert.equal(model.get('currentPage'), 1, 'currentPage');
assert.equal(model.get('canLoadMore'), true, 'canLoadMore');
assert.notOk(route.get('_extraParams'), 'extra params are empty');
assert.ok(!model.get('reachedInfinity'), 'Should not reach infinity');
});
test('it allows customizations of request params', function(assert) {
let store = this.createMockStore(
this.EA([]),
(modelType, findQuery) => {
assert.deepEqual(findQuery, { per: 25, p: 1 }, 'findQuery');
}
);
let route = this.createRoute(
['item', {
perPageParam: 'per', pageParam: 'p'
}],
{ store }
);
this.callModelHook(route);
});
test('It allows to set startingPage as 0', function(assert) {
let store = this.createMockStore( this.EA([{id: 1, name: 'Test'}], { total_pages: 1 }) );
let route = this.createRoute(
['item', {
startingPage: 0
}],
{ store }
);
let model = this.callModelHook(route);
assert.equal(model.get('currentPage'), 0);
assert.equal(model.get('canLoadMore'), false);
});
test('it skips request params when set to null', function(assert) {
let store = this.createMockStore(
this.EA([]),
(modelType, findQuery) => {
assert.deepEqual(findQuery, {}, 'findQuery');
});
let route = this.createRoute(
['item', {
perPageParam: null, pageParam: null
}],
{ store }
);
this.callModelHook(route);
});
test('it allows customizations of meta parsing params', function(assert) {
let store = this.createMockStore(
this.EA([{id: 1, name: 'Walter White'}], { pagination: { total: 22 } })
);
let route = this.createRoute(
['item', {
totalPagesParam: 'pagination.total',
}],
{ store }
);
let model = this.callModelHook(route);
assert.equal(model.get('totalPagesParam'), 'pagination.total', 'totalPagesParam');
assert.equal(model.get('_totalPages'), 22, '_totalPages');
});
test('it allows customizations of meta count params', function(assert) {
let store = this.createMockStore(
this.EA([{id: 1, name: 'Walter White'}], { pagination: { records: 22 } })
);
let route = this.createRoute(
['item', { countParam: 'pagination.records', }],
{ store }
);
let model = this.callModelHook(route);
assert.equal(model.get('countParam'), 'pagination.records', 'countParam');
assert.equal(model.get('_count'), 22, '_count');
});
test('it copies arbitrary model hook meta from route request to the infinityModel', function(assert) {
let store = this.createMockStore(
this.EA([{id: 1, name: 'Walter White'}], { meta: { meaningOfLife: 42 }})
);
let route = this.createRoute(['item', {}], { store });
let model = this.callModelHook(route);
assert.equal(model.get('meta.meaningOfLife'), 42, 'meta');
});
});
module('RouteMixin - reaching the end', function(hooks) {
hooks.beforeEach(function() {
this.createRouteWithStore = (extras, boundParamsOrInfinityModel) => {
let store = this.createMockStore(this.EA([{id: 1, name: 'Test'}], { meta: { total_pages: 2 } }));
this.route = this.createRoute(['item', extras, boundParamsOrInfinityModel], { store });
this.callModelHookWithStore();
};
this.callModelHookWithStore = () => {
this.model = this.callModelHook(this.route);
};
this.loadMore = () => {
run(() => {
this.route.infinity.infinityLoad(this.model);
});
};
});
test('it sets state when it reaches the end', function (assert) {
assert.expect(4);
this.createRouteWithStore({ startingPage: 2 });
assert.equal(this.model.get('_totalPages'), 2, '_totalPages');
assert.equal(this.model.get('currentPage'), 2, 'currentPage');
assert.equal(this.model.get('canLoadMore'), false, 'canLoadMore');
assert.ok(this.model.get('reachedInfinity'), 'Should reach infinity');
});
test('it uses extra params when loading more data', function (assert) {
assert.expect(4);
this.createRouteWithStore({ extra: 'param' });
// assert.equal(this.model.get('_extraParams.extra'), 'param', '_extraParams.extra');
assert.equal(this.model.get('canLoadMore'), true, 'canLoadMore');
this.loadMore();
// assert.equal(this.model.get('_extraParams.extra'), 'param', '_extraParams.extra');
assert.equal(this.model.get('canLoadMore'), false, 'canLoadMore');
assert.equal(this.model.get('currentPage'), 2, 'currentPage');
assert.ok(this.model.get('reachedInfinity'), 'Should reach infinity');
});
test('route accepts an instance of InfinityModel as the third argument', function (assert) {
assert.expect(3);
let ExtendedInfinityModel = InfinityModel.extend({
customId: 2,
buildParams() {
let params = this._super(...arguments);
params['custom_id'] = get(this, 'customId');
return params;
}
});
this.createRouteWithStore({ extra: 'param' }, ExtendedInfinityModel);
assert.equal(this.model instanceof InfinityModel, true, 'model is instance of extended infinity model');
this.loadMore();
assert.equal(this.model instanceof InfinityModel, true, 'model is instance of extended infinity model');
assert.ok(this.model.get('reachedInfinity'), 'Should reach infinity');
});
test('route accepts an instance of InfinityModel as the third argument with passed in param', function (assert) {
assert.expect(2);
let ExtendedInfinityModel = InfinityModel.extend({
buildParams() {
let params = this._super(...arguments);
params['custom_id'] = get(this, 'custom.id');
assert.equal(params['custom_id'], 2);
return params;
}
});
// imagine 'custom' being some type of service that holds state
this.createRouteWithStore({ extra: 'param' }, ExtendedInfinityModel.extend({ custom: { id : 2 } }));
this.loadMore();
});
test('route does not detect boundParams when no boundParams passed', function (assert) {
assert.expect(1);
this.createRouteWithStore({ extra: 'param' });
assert.equal(this.model.get('_deprecatedBoundParams'), undefined, 'bound params is not detected');
});
test("It doesn't request more pages once canLoadMore is false", function (assert) {
assert.expect(6);
this.createRouteWithStore();
assert.ok(this.model.get('canLoadMore'), 'can load more');
assert.equal(this.model.get('currentPage'), 1, 'currentPage');
this.loadMore();
assert.notOk(this.model.get('canLoadMore'), 'can load more');
assert.equal(this.model.get('currentPage'), 2, 'currentPage');
this.loadMore();
assert.notOk(this.model.get('canLoadMore'), 'can load more');
assert.equal(this.model.get('currentPage'), 2, 'currentPage');
});
test("It resets the currentPage when the model hook is called again", function (assert) {
assert.expect(5);
this.createRouteWithStore();
assert.ok(this.model.get('canLoadMore'), 'can load more');
assert.equal(this.model.get('currentPage'), 1, 'currentPage');
this.callModelHookWithStore();
assert.equal(this.model.get('currentPage'), 1, 'currentPage');
this.loadMore();
assert.equal(this.model.get('currentPage'), 2, 'currentPage');
this.callModelHookWithStore();
assert.equal(this.model.get('currentPage'), 1, 'currentPage');
});
});
module('RouteMixin - loading more data', function(hooks) {
hooks.beforeEach(function(assert) {
let store = this.createMockStore(
// return response for model hook
this.EA([{id: 1, name: 'Test'}, {id: 2, name: 'Test 2'}], { meta: { testTotalPages: 3 } }),
// for query method in model hook
(modelType, findQuery) => {
assert.equal(findQuery.testPerPage, 1);
assert.equal(findQuery.testPage, this.expectedPageNumber);
}
);
this.route = this.createRoute(
['item', {
// explicit params passed to infinityModel hook
perPage: 1, startingPage: 2,
totalPagesParam: 'meta.testTotalPages', perPageParam: 'testPerPage', pageParam: 'testPage'
}],
{ store: store }
);
this.expectedPageNumber = 2;
this.model = this.callModelHook(this.route);
});
test('it uses overridden params when loading more data', function (assert) {
assert.expect(4);
this.expectedPageNumber = 2;
assert.equal(this.model.get('canLoadMore'), true, 'canLoadMore');
assert.equal(this.model.get('currentPage'), 2, 'currentPage');
});
test('it uses overridden params when reaching the end', function (assert) {
assert.expect(7);
this.expectedPageNumber = 3;
const infinityModel = this.route.get('infinity.infinityModels').objectAt(0);
run(() => {
this.route.infinity.infinityLoad(infinityModel);
});
assert.equal(this.model.get('canLoadMore'), false, 'canLoadMore');
assert.equal(this.model.get('currentPage'), 3, 'currentPage');
assert.ok(this.model.get('reachedInfinity'), 'Should reach infinity');
});
});
});
import { addEventListener } from '../modules/events.js'
import {
isArray,
looseEqual,
looseIndexOf,
invokeArrayFns,
toNumber,
isSet
} from '../../shared/index.js'
const getModelAssigner = vnode => {
const fn = vnode.props['onUpdate:modelValue']
return isArray(fn) ? value => invokeArrayFns(fn, value) : fn
}
function onCompositionStart (e) {
e.target.composing = true
}
function onCompositionEnd (e) {
const target = e.target
if (target.composing) {
target.composing = false
trigger(target, 'input')
}
}
function trigger (el, type) {
const e = document.createEvent('HTMLEvents')
e.initEvent(type, true, true)
el.dispatchEvent(e)
}
// We are exporting the v-model runtime directly as vnode hooks so that it can
// be tree-shaken in case v-model is never used.
export const vModelText = {
created (el, { modifiers: { lazy, trim, number } }, vnode) {
el._assign = getModelAssigner(vnode)
const castToNumber =
number || (vnode.props && vnode.props.type === 'number')
addEventListener(el, lazy ? 'change' : 'input', e => {
if (e.target.composing) return
let domValue = el.value
if (trim) {
domValue = domValue.trim()
} else if (castToNumber) {
domValue = toNumber(domValue)
}
el._assign(domValue)
})
if (trim) {
addEventListener(el, 'change', () => {
el.value = el.value.trim()
})
}
if (!lazy) {
addEventListener(el, 'compositionstart', onCompositionStart)
addEventListener(el, 'compositionend', onCompositionEnd)
// Safari < 10.2 & UIWebView doesn't fire compositionend when
// switching focus before confirming composition choice
// this also fixes the issue where some browsers e.g. iOS Chrome
// fires "change" instead of "input" on autocomplete.
addEventListener(el, 'change', onCompositionEnd)
}
},
// set value on mounted so it's after min/max for type="range"
mounted (el, { value }) {
el.value = value == null ? '' : value
},
beforeUpdate (el, { value, modifiers: { lazy, trim, number } }, vnode) {
el._assign = getModelAssigner(vnode)
// avoid clearing unresolved text. #2302
if (el.composing) return
if (document.activeElement === el) {
if (lazy) {
return
}
if (trim && el.value.trim() === value) {
return
}
if ((number || el.type === 'number') && toNumber(el.value) === value) {
return
}
}
const newValue = value == null ? '' : value
if (el.value !== newValue) {
el.value = newValue
}
}
}
export const vModelCheckbox = {
// #4096 array checkboxes need to be deep traversed
deep: true,
created (el, _, vnode) {
el._assign = getModelAssigner(vnode)
addEventListener(el, 'change', () => {
const modelValue = el._modelValue
const elementValue = getValue(el)
const checked = el.checked
const assign = el._assign
if (isArray(modelValue)) {
const index = looseIndexOf(modelValue, elementValue)
const found = index !== -1
if (checked && !found) {
assign(modelValue.concat(elementValue))
} else if (!checked && found) {
const filtered = [...modelValue]
filtered.splice(index, 1)
assign(filtered)
}
} else if (isSet(modelValue)) {
const cloned = new Set(modelValue)
if (checked) {
cloned.add(elementValue)
} else {
cloned.delete(elementValue)
}
assign(cloned)
} else {
assign(getCheckboxValue(el, checked))
}
})
},
// set initial checked on mount to wait for true-value/false-value
mounted: setChecked,
beforeUpdate (el, binding, vnode) {
el._assign = getModelAssigner(vnode)
setChecked(el, binding, vnode)
}
}
function setChecked (el, { value, oldValue }, vnode) {
el._modelValue = value
if (isArray(value)) {
el.checked = looseIndexOf(value, vnode.props.value) > -1
} else if (isSet(value)) {
el.checked = value.has(vnode.props.value)
} else if (value !== oldValue) {
el.checked = looseEqual(value, getCheckboxValue(el, true))
}
}
export const vModelRadio = {
created (el, { value }, vnode) {
el.checked = looseEqual(value, vnode.props.value)
el._assign = getModelAssigner(vnode)
addEventListener(el, 'change', () => {
el._assign(getValue(el))
})
},
beforeUpdate (el, { value, oldValue }, vnode) {
el._assign = getModelAssigner(vnode)
if (value !== oldValue) {
el.checked = looseEqual(value, vnode.props.value)
}
}
}
export const vModelSelect = {
// <select multiple> value need to be deep traversed
deep: true,
created (el, { value, modifiers: { number } }, vnode) {
const isSetModel = isSet(value)
addEventListener(el, 'change', () => {
const selectedVal = Array.prototype.filter
.call(el.options, o => o.selected)
.map(o => (number ? toNumber(getValue(o)) : getValue(o)))
el._assign(
el.multiple
? isSetModel
? new Set(selectedVal)
: selectedVal
: selectedVal[0]
)
})
el._assign = getModelAssigner(vnode)
},
// set value in mounted & updated because <select> relies on its children
// <option>s.
mounted (el, { value }) {
setSelected(el, value)
},
beforeUpdate (el, _binding, vnode) {
el._assign = getModelAssigner(vnode)
},
updated (el, { value }) {
setSelected(el, value)
}
}
function setSelected (el, value) {
const isMultiple = el.multiple
for (let i = 0, l = el.options.length; i < l; i++) {
const option = el.options[i]
const optionValue = getValue(option)
if (isMultiple) {
if (isArray(value)) {
option.selected = looseIndexOf(value, optionValue) > -1
} else {
option.selected = value.has(optionValue)
}
} else {
if (looseEqual(getValue(option), value)) {
if (el.selectedIndex !== i) el.selectedIndex = i
return
}
}
}
if (!isMultiple && el.selectedIndex !== -1) {
el.selectedIndex = -1
}
}
// retrieve raw value set via :value bindings
function getValue (el) {
return '_value' in el ? el._value : el.value
}
// retrieve raw value for true-value and false-value set via :true-value or :false-value bindings
function getCheckboxValue (el, checked) {
const key = checked ? '_trueValue' : '_falseValue'
return key in el ? el[key] : checked
}
export const vModelDynamic = {
created (el, binding, vnode) {
callModelHook(el, binding, vnode, null, 'created')
},
mounted (el, binding, vnode) {
callModelHook(el, binding, vnode, null, 'mounted')
},
beforeUpdate (el, binding, vnode, prevVNode) {
callModelHook(el, binding, vnode, prevVNode, 'beforeUpdate')
},
updated (el, binding, vnode, prevVNode) {
callModelHook(el, binding, vnode, prevVNode, 'updated')
}
}
function callModelHook (el, binding, vnode, prevVNode, hook) {
let modelToUse
switch (el.tagName) {
case 'SELECT':
modelToUse = vModelSelect
break
case 'TEXTAREA':
modelToUse = vModelText
break
default:
switch (vnode.props && vnode.props.type) {
case 'checkbox':
modelToUse = vModelCheckbox
break
case 'radio':
modelToUse = vModelRadio
break
default:
modelToUse = vModelText
}
}
const fn = modelToUse[hook]
fn && fn(el, binding, vnode, prevVNode)
}
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var runtimeCore = require('@vue/runtime-core');
require('@vue/compiler-dom');
const doc = document;
const svgNS = 'http://www.w3.org/2000/svg';
const nodeOps = {
insert: (child, parent, anchor) => {
if (anchor != null) {
parent.insertBefore(child, anchor);
}
else {
parent.appendChild(child);
}
},
remove: (child) => {
const parent = child.parentNode;
if (parent != null) {
parent.removeChild(child);
}
},
createElement: (tag, isSVG) => isSVG ? doc.createElementNS(svgNS, tag) : doc.createElement(tag),
createText: (text) => doc.createTextNode(text),
createComment: (text) => doc.createComment(text),
setText: (node, text) => {
node.nodeValue = text;
},
setElementText: (el, text) => {
el.textContent = text;
},
parentNode: (node) => node.parentNode,
nextSibling: (node) => node.nextSibling,
querySelector: (selector) => doc.querySelector(selector)
};
// compiler should normalize class + :class bindings on the same element
// into a single binding ['staticClass', dynamic]
function patchClass(el, value, isSVG) {
// directly setting className should be faster than setAttribute in theory
if (isSVG) {
el.setAttribute('class', value);
}
else {
el.className = value;
}
}
const EMPTY_OBJ = {};
const isOn = (key) => key[0] === 'o' && key[1] === 'n';
const isArray = Array.isArray;
const isString = (val) => typeof val === 'string';
const isObject = (val) => val !== null && typeof val === 'object';
function patchStyle(el, prev, next) {
const style = el.style;
if (!next) {
el.removeAttribute('style');
}
else if (isString(next)) {
style.cssText = next;
}
else {
for (const key in next) {
style[key] = next[key];
}
if (prev && !isString(prev)) {
for (const key in prev) {
if (!next[key]) {
style[key] = '';
}
}
}
}
}
function patchAttr(el, key, value) {
if (value == null) {
el.removeAttribute(key);
}
else {
el.setAttribute(key, value);
}
}
function patchDOMProp(el, key, value,
// the following args are passed only due to potential innerHTML/textContent
// overriding existing VNodes, in which case the old tree must be properly
// unmounted.
prevChildren, parentComponent, parentSuspense, unmountChildren) {
if ((key === 'innerHTML' || key === 'textContent') && prevChildren != null) {
unmountChildren(prevChildren, parentComponent, parentSuspense);
}
if (key === 'value' && el.tagName !== 'PROGRESS') {
// store value as _value as well since
// non-string values will be stringified.
el._value = value;
}
if (value === '' && typeof el[key] === 'boolean') {
// e.g. <select multiple> compiles to { multiple: '' }
el[key] = true;
}
else {
el[key] = value == null ? '' : value;
}
}
// Async edge case fix requires storing an event listener's attach timestamp.
let _getNow = Date.now;
// Determine what event timestamp the browser is using. Annoyingly, the
// timestamp can either be hi-res ( relative to page load) or low-res
// (relative to UNIX epoch), so in order to compare time we have to use the
// same timestamp type when saving the flush timestamp.
if (typeof document !== 'undefined' &&
_getNow() > document.createEvent('Event').timeStamp) {
// if the low-res timestamp which is bigger than the event timestamp
// (which is evaluated AFTER) it means the event is using a hi-res timestamp,
// and we need to use the hi-res version for event listeners as well.
_getNow = () => performance.now();
}
// To avoid the overhead of repeatedly calling performance.now(), we cache
// and use the same timestamp for all event listeners attached in the same tick.
let cachedNow = 0;
const p = Promise.resolve();
const reset = () => {
cachedNow = 0;
};
const getNow = () => cachedNow || (p.then(reset), (cachedNow = _getNow()));
function addEventListener(el, event, handler, options) {
el.addEventListener(event, handler, options);
}
function removeEventListener(el, event, handler, options) {
el.removeEventListener(event, handler, options);
}
function patchEvent(el, name, prevValue, nextValue, instance = null) {
const prevOptions = prevValue && 'options' in prevValue && prevValue.options;
const nextOptions = nextValue && 'options' in nextValue && nextValue.options;
const invoker = prevValue && prevValue.invoker;
const value = nextValue && 'handler' in nextValue ? nextValue.handler : nextValue;
if (prevOptions || nextOptions) {
const prev = prevOptions || EMPTY_OBJ;
const next = nextOptions || EMPTY_OBJ;
if (prev.capture !== next.capture ||
prev.passive !== next.passive ||
prev.once !== next.once) {
if (invoker) {
removeEventListener(el, name, invoker, prev);
}
if (nextValue && value) {
const invoker = createInvoker(value, instance);
nextValue.invoker = invoker;
addEventListener(el, name, invoker, next);
}
return;
}
}
if (nextValue && value) {
if (invoker) {
prevValue.invoker = null;
invoker.value = value;
nextValue.invoker = invoker;
invoker.lastUpdated = getNow();
}
else {
addEventListener(el, name, createInvoker(value, instance), nextOptions || void 0);
}
}
else if (invoker) {
removeEventListener(el, name, invoker, prevOptions || void 0);
}
}
function createInvoker(initialValue, instance) {
const invoker = (e) => {
// async edge case #6566: inner click event triggers patch, event handler
// attached to outer element during patch, and triggered again. This
// happens because browsers fire microtask ticks between event propagation.
// the solution is simple: we save the timestamp when a handler is attached,
// and the handler would only fire if the event passed to it was fired
// AFTER it was attached.
if (e.timeStamp >= invoker.lastUpdated - 1) {
runtimeCore.callWithAsyncErrorHandling(invoker.value, instance, 5 /* NATIVE_EVENT_HANDLER */, [e]);
}
};
invoker.value = initialValue;
initialValue.invoker = invoker;
invoker.lastUpdated = getNow();
return invoker;
}
function patchProp(el, key, nextValue, prevValue, isSVG, prevChildren, parentComponent, parentSuspense, unmountChildren) {
switch (key) {
// special
case 'class':
patchClass(el, nextValue, isSVG);
break;
case 'style':
patchStyle(el, prevValue, nextValue);
break;
case 'modelValue':
case 'onUpdate:modelValue':
// Do nothing. This is handled by v-model directives.
break;
default:
if (isOn(key)) {
patchEvent(el, key.slice(2).toLowerCase(), prevValue, nextValue, parentComponent);
}
else if (!isSVG && key in el) {
patchDOMProp(el, key, nextValue, prevChildren, parentComponent, parentSuspense, unmountChildren);
}
else {
patchAttr(el, key, nextValue);
}
break;
}
}
const getModelAssigner = (vnode) => vnode.props['onUpdate:modelValue'];
function onCompositionStart(e) {
e.target.composing = true;
}
function onCompositionEnd(e) {
const target = e.target;
if (target.composing) {
target.composing = false;
trigger(target, 'input');
}
}
function trigger(el, type) {
const e = document.createEvent('HTMLEvents');
e.initEvent(type, true, true);
el.dispatchEvent(e);
}
function toNumber(val) {
const n = parseFloat(val);
return isNaN(n) ? val : n;
}
// We are exporting the v-model runtime directly as vnode hooks so that it can
// be tree-shaken in case v-model is never used.
const vModelText = {
beforeMount(el, { value, modifiers: { lazy, trim, number } }, vnode) {
el.value = value;
const assign = getModelAssigner(vnode);
const castToNumber = number || el.type === 'number';
addEventListener(el, lazy ? 'change' : 'input', () => {
let domValue = el.value;
if (trim) {
domValue = domValue.trim();
}
else if (castToNumber) {
domValue = toNumber(domValue);
}
assign(domValue);
});
if (trim) {
addEventListener(el, 'change', () => {
el.value = el.value.trim();
});
}
if (!lazy) {
addEventListener(el, 'compositionstart', onCompositionStart);
addEventListener(el, 'compositionend', onCompositionEnd);
// Safari < 10.2 & UIWebView doesn't fire compositionend when
// switching focus before confirming composition choice
// this also fixes the issue where some browsers e.g. iOS Chrome
// fires "change" instead of "input" on autocomplete.
addEventListener(el, 'change', onCompositionEnd);
}
},
beforeUpdate(el, { value, modifiers: { trim, number } }) {
if (document.activeElement === el) {
if (trim && el.value.trim() === value) {
return;
}
if ((number || el.type === 'number') && toNumber(el.value) === value) {
return;
}
}
el.value = value;
}
};
const vModelCheckbox = {
beforeMount(el, binding, vnode) {
setChecked(el, binding, vnode);
const assign = getModelAssigner(vnode);
addEventListener(el, 'change', () => {
const modelValue = el._modelValue;
const elementValue = getValue(el);
const checked = el.checked;
if (isArray(modelValue)) {
const index = looseIndexOf(modelValue, elementValue);
const found = index !== -1;
if (checked && !found) {
assign(modelValue.concat(elementValue));
}
else if (!checked && found) {
const filtered = [...modelValue];
filtered.splice(index, 1);
assign(filtered);
}
}
else {
assign(checked);
}
});
},
beforeUpdate: setChecked
};
function setChecked(el, { value }, vnode) {
el._modelValue = value;
el.checked = isArray(value)
? looseIndexOf(value, vnode.props.value) > -1
: !!value;
}
const vModelRadio = {
beforeMount(el, { value }, vnode) {
el.checked = looseEqual(value, vnode.props.value);
const assign = getModelAssigner(vnode);
addEventListener(el, 'change', () => {
assign(getValue(el));
});
},
beforeUpdate(el, { value }, vnode) {
el.checked = looseEqual(value, vnode.props.value);
}
};
const vModelSelect = {
// use mounted & updated because <select> relies on its children <option>s.
mounted(el, { value }, vnode) {
setSelected(el, value);
const assign = getModelAssigner(vnode);
addEventListener(el, 'change', () => {
const selectedVal = Array.prototype.filter
.call(el.options, (o) => o.selected)
.map(getValue);
assign(el.multiple ? selectedVal : selectedVal[0]);
});
},
updated(el, { value }) {
setSelected(el, value);
}
};
function setSelected(el, value) {
const isMultiple = el.multiple;
if (isMultiple && !isArray(value)) {
return;
}
for (let i = 0, l = el.options.length; i < l; i++) {
const option = el.options[i];
const optionValue = getValue(option);
if (isMultiple) {
option.selected = looseIndexOf(value, optionValue) > -1;
}
else {
if (looseEqual(getValue(option), value)) {
el.selectedIndex = i;
return;
}
}
}
if (!isMultiple) {
el.selectedIndex = -1;
}
}
function looseEqual(a, b) {
if (a === b)
return true;
const isObjectA = isObject(a);
const isObjectB = isObject(b);
if (isObjectA && isObjectB) {
try {
const isArrayA = isArray(a);
const isArrayB = isArray(b);
if (isArrayA && isArrayB) {
return (a.length === b.length &&
a.every((e, i) => looseEqual(e, b[i])));
}
else if (a instanceof Date && b instanceof Date) {
return a.getTime() === b.getTime();
}
else if (!isArrayA && !isArrayB) {
const keysA = Object.keys(a);
const keysB = Object.keys(b);
return (keysA.length === keysB.length &&
keysA.every(key => looseEqual(a[key], b[key])));
}
else {
/* istanbul ignore next */
return false;
}
}
catch (e) {
/* istanbul ignore next */
return false;
}
}
else if (!isObjectA && !isObjectB) {
return String(a) === String(b);
}
else {
return false;
}
}
function looseIndexOf(arr, val) {
return arr.findIndex(item => looseEqual(item, val));
}
// retrieve raw value set via :value bindings
function getValue(el) {
return '_value' in el ? el._value : el.value;
}
const vModelDynamic = {
beforeMount(el, binding, vnode) {
callModelHook(el, binding, vnode, null, 'beforeMount');
},
mounted(el, binding, vnode) {
callModelHook(el, binding, vnode, null, 'mounted');
},
beforeUpdate(el, binding, vnode, prevVNode) {
callModelHook(el, binding, vnode, prevVNode, 'beforeUpdate');
},
updated(el, binding, vnode, prevVNode) {
callModelHook(el, binding, vnode, prevVNode, 'updated');
}
};
function callModelHook(el, binding, vnode, prevVNode, hook) {
let modelToUse;
switch (el.tagName) {
case 'SELECT':
modelToUse = vModelSelect;
break;
case 'TEXTAREA':
modelToUse = vModelText;
break;
default:
switch (el.type) {
case 'checkbox':
modelToUse = vModelCheckbox;
break;
case 'radio':
modelToUse = vModelRadio;
break;
default:
modelToUse = vModelText;
}
}
const fn = modelToUse[hook];
fn && fn(el, binding, vnode, prevVNode);
}
const systemModifiers = ['ctrl', 'shift', 'alt', 'meta'];
const modifierGuards = {
stop: e => e.stopPropagation(),
prevent: e => e.preventDefault(),
self: e => e.target !== e.currentTarget,
ctrl: e => !e.ctrlKey,
shift: e => !e.shiftKey,
alt: e => !e.altKey,
meta: e => !e.metaKey,
left: e => 'button' in e && e.button !== 0,
middle: e => 'button' in e && e.button !== 1,
right: e => 'button' in e && e.button !== 2,
exact: (e, modifiers) => systemModifiers.some(m => e[`${m}Key`] && !modifiers.includes(m))
};
const withModifiers = (fn, modifiers) => {
return (event) => {
for (let i = 0; i < modifiers.length; i++) {
const guard = modifierGuards[modifiers[i]];
if (guard && guard(event, modifiers))
return;
}
return fn(event);
};
};
// Kept for 2.x compat.
// Note: IE11 compat for `spacebar` and `del` is removed for now.
const keyNames = {
esc: 'escape',
space: ' ',
up: 'arrowup',
left: 'arrowleft',
right: 'arrowright',
down: 'arrowdown',
delete: 'backspace'
};
const withKeys = (fn, modifiers) => {
return (event) => {
if (!('key' in event))
return;
const eventKey = event.key.toLowerCase();
if (
// None of the provided key modifiers match the current event key
!modifiers.some(k => k === eventKey || keyNames[k] === eventKey)) {
return;
}
return fn(event);
};
};
const { render, createApp } = runtimeCore.createRenderer({
patchProp,
...nodeOps
});
const exportedCreateApp = createApp;
Object.keys(runtimeCore).forEach(function (k) {
if (k !== 'default') Object.defineProperty(exports, k, {
enumerable: true,
get: function () {
return runtimeCore[k];
}
});
});
exports.createApp = exportedCreateApp;
exports.render = render;
exports.vModelCheckbox = vModelCheckbox;
exports.vModelDynamic = vModelDynamic;
exports.vModelRadio = vModelRadio;
exports.vModelSelect = vModelSelect;
exports.vModelText = vModelText;
exports.withKeys = withKeys;
exports.withModifiers = withModifiers;
Accelerate Your Automation Test Cycles With LambdaTest
Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.