The old style of merging all of a function's variable declarations into a single statement made some sense back in the days of var, but there's no reason to keep doing it now that we use const and let.
730 lines
21 KiB
JavaScript
730 lines
21 KiB
JavaScript
describe('SpyRegistry', function() {
|
|
function createSpy(name, originalFn) {
|
|
return privateUnderTest.Spy(name, originalFn);
|
|
}
|
|
|
|
describe('#spyOn', function() {
|
|
it('checks for the existence of the object', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
createSpy: createSpy
|
|
});
|
|
expect(function() {
|
|
spyRegistry.spyOn(void 0, 'pants');
|
|
}).toThrowError(/could not find an object/);
|
|
});
|
|
|
|
it('checks that a method name was passed', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry();
|
|
const target = {};
|
|
|
|
expect(function() {
|
|
spyRegistry.spyOn(target);
|
|
}).toThrowError(/No method name supplied/);
|
|
});
|
|
|
|
it('checks that the object is not `null`', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry();
|
|
expect(function() {
|
|
spyRegistry.spyOn(null, 'pants');
|
|
}).toThrowError(/could not find an object/);
|
|
});
|
|
|
|
it('checks that the method name is not `null`', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry();
|
|
const target = {};
|
|
|
|
expect(function() {
|
|
spyRegistry.spyOn(target, null);
|
|
}).toThrowError(/No method name supplied/);
|
|
});
|
|
|
|
it('checks for the existence of the method', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry();
|
|
const target = {};
|
|
|
|
expect(function() {
|
|
spyRegistry.spyOn(target, 'pants');
|
|
}).toThrowError(/method does not exist/);
|
|
});
|
|
|
|
it('checks if it has already been spied upon', function() {
|
|
const spies = [];
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
currentSpies: function() {
|
|
return spies;
|
|
},
|
|
createSpy: createSpy
|
|
});
|
|
const target = { spiedFunc: function() {} };
|
|
|
|
spyRegistry.spyOn(target, 'spiedFunc');
|
|
|
|
expect(function() {
|
|
spyRegistry.spyOn(target, 'spiedFunc');
|
|
}).toThrowError(/has already been spied upon/);
|
|
});
|
|
|
|
it('checks if it can be spied upon', function() {
|
|
const scope = {};
|
|
|
|
function myFunc() {
|
|
return 1;
|
|
}
|
|
|
|
Object.defineProperty(scope, 'myFunc', {
|
|
get: function() {
|
|
return myFunc;
|
|
}
|
|
});
|
|
|
|
const spies = [];
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
currentSpies: function() {
|
|
return spies;
|
|
}
|
|
});
|
|
const target = { spiedFunc: scope.myFunc };
|
|
|
|
expect(function() {
|
|
spyRegistry.spyOn(scope, 'myFunc');
|
|
}).toThrowError(/is not declared writable or has no setter/);
|
|
|
|
expect(function() {
|
|
spyRegistry.spyOn(target, 'spiedFunc');
|
|
}).not.toThrowError(/is not declared writable or has no setter/);
|
|
});
|
|
|
|
it('throws if assigning to the property is a no-op', function() {
|
|
const scope = {};
|
|
|
|
function original() {
|
|
return 1;
|
|
}
|
|
|
|
Object.defineProperty(scope, 'myFunc', {
|
|
get() {
|
|
return original;
|
|
},
|
|
set() {}
|
|
});
|
|
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
createSpy: createSpy
|
|
});
|
|
expect(function() {
|
|
spyRegistry.spyOn(scope, 'myFunc');
|
|
}).toThrowError(
|
|
"<spyOn> : Can't spy on myFunc because assigning to it had no effect"
|
|
);
|
|
});
|
|
|
|
it('overrides the method on the object and returns the spy', function() {
|
|
let originalFunctionWasCalled = false;
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
createSpy: createSpy
|
|
});
|
|
const target = {
|
|
spiedFunc: function() {
|
|
originalFunctionWasCalled = true;
|
|
}
|
|
};
|
|
|
|
const spy = spyRegistry.spyOn(target, 'spiedFunc');
|
|
|
|
expect(target.spiedFunc).toEqual(spy);
|
|
target.spiedFunc();
|
|
expect(originalFunctionWasCalled).toBe(false);
|
|
});
|
|
|
|
it('throws if the method is a mock clock method', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
createSpy: createSpy
|
|
});
|
|
const target = { spiedFunc: function() {} };
|
|
target.spiedFunc[privateUnderTest.Clock.IsMockClockTimingFn] = true;
|
|
|
|
expect(function() {
|
|
spyRegistry.spyOn(target, 'spiedFunc');
|
|
}).toThrowError("Mock clock timing functions can't be spied on");
|
|
});
|
|
});
|
|
|
|
describe('#spyOnProperty', function() {
|
|
it('checks for the existence of the object', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry();
|
|
expect(function() {
|
|
spyRegistry.spyOnProperty(void 0, 'pants');
|
|
}).toThrowError(/could not find an object/);
|
|
});
|
|
|
|
it('checks that a property name was passed', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry();
|
|
const target = {};
|
|
|
|
expect(function() {
|
|
spyRegistry.spyOnProperty(target);
|
|
}).toThrowError(/No property name supplied/);
|
|
});
|
|
|
|
it('checks for the existence of the method', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry();
|
|
const target = {};
|
|
|
|
expect(function() {
|
|
spyRegistry.spyOnProperty(target, 'pants');
|
|
}).toThrowError(/property does not exist/);
|
|
});
|
|
|
|
it('checks for the existence of access type', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry();
|
|
const target = {};
|
|
|
|
Object.defineProperty(target, 'pants', {
|
|
get: function() {
|
|
return 1;
|
|
},
|
|
configurable: true
|
|
});
|
|
|
|
expect(function() {
|
|
spyRegistry.spyOnProperty(target, 'pants', 'set');
|
|
}).toThrowError(/does not have access type/);
|
|
});
|
|
|
|
it('checks if it can be spied upon', function() {
|
|
const target = {};
|
|
|
|
Object.defineProperty(target, 'myProp', {
|
|
get: function() {}
|
|
});
|
|
|
|
Object.defineProperty(target, 'spiedProp', {
|
|
get: function() {},
|
|
configurable: true
|
|
});
|
|
|
|
const spyRegistry = new privateUnderTest.SpyRegistry();
|
|
|
|
expect(function() {
|
|
spyRegistry.spyOnProperty(target, 'myProp');
|
|
}).toThrowError(/is not declared configurable/);
|
|
|
|
expect(function() {
|
|
spyRegistry.spyOnProperty(target, 'spiedProp');
|
|
}).not.toThrowError(/is not declared configurable/);
|
|
});
|
|
|
|
it('overrides the property getter on the object and returns the spy', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
createSpy: createSpy
|
|
});
|
|
const target = {};
|
|
const returnValue = 1;
|
|
|
|
Object.defineProperty(target, 'spiedProperty', {
|
|
get: function() {
|
|
return returnValue;
|
|
},
|
|
configurable: true
|
|
});
|
|
|
|
expect(target.spiedProperty).toEqual(returnValue);
|
|
|
|
const spy = spyRegistry.spyOnProperty(target, 'spiedProperty');
|
|
const getter = Object.getOwnPropertyDescriptor(target, 'spiedProperty')
|
|
.get;
|
|
|
|
expect(getter).toEqual(spy);
|
|
expect(target.spiedProperty).toBeUndefined();
|
|
});
|
|
|
|
it('overrides the property setter on the object and returns the spy', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
createSpy: createSpy
|
|
});
|
|
const target = {};
|
|
const returnValue = 1;
|
|
|
|
Object.defineProperty(target, 'spiedProperty', {
|
|
get: function() {
|
|
return returnValue;
|
|
},
|
|
set: function() {},
|
|
configurable: true
|
|
});
|
|
|
|
const spy = spyRegistry.spyOnProperty(target, 'spiedProperty', 'set');
|
|
const setter = Object.getOwnPropertyDescriptor(target, 'spiedProperty')
|
|
.set;
|
|
|
|
expect(target.spiedProperty).toEqual(returnValue);
|
|
expect(setter).toEqual(spy);
|
|
});
|
|
|
|
describe('when the property is already spied upon', function() {
|
|
it('throws an error if respy is not allowed', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
createSpy: createSpy
|
|
});
|
|
const target = {};
|
|
|
|
Object.defineProperty(target, 'spiedProp', {
|
|
get: function() {
|
|
return 1;
|
|
},
|
|
configurable: true
|
|
});
|
|
|
|
spyRegistry.spyOnProperty(target, 'spiedProp');
|
|
|
|
expect(function() {
|
|
spyRegistry.spyOnProperty(target, 'spiedProp');
|
|
}).toThrowError(/spiedProp#get has already been spied upon/);
|
|
});
|
|
|
|
it('returns the original spy if respy is allowed', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
createSpy: createSpy
|
|
});
|
|
const target = {};
|
|
|
|
spyRegistry.allowRespy(true);
|
|
|
|
Object.defineProperty(target, 'spiedProp', {
|
|
get: function() {
|
|
return 1;
|
|
},
|
|
configurable: true
|
|
});
|
|
|
|
const originalSpy = spyRegistry.spyOnProperty(target, 'spiedProp');
|
|
|
|
expect(spyRegistry.spyOnProperty(target, 'spiedProp')).toBe(
|
|
originalSpy
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#spyOnAllFunctions', function() {
|
|
it('checks for the existence of the object', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry();
|
|
expect(function() {
|
|
spyRegistry.spyOnAllFunctions(void 0);
|
|
}).toThrowError(/spyOnAllFunctions could not find an object to spy upon/);
|
|
});
|
|
|
|
it('overrides all writable and configurable functions of the object and its parents', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
createSpy: function() {
|
|
return 'I am a spy';
|
|
}
|
|
});
|
|
const createNoop = function() {
|
|
return function() {
|
|
/**/
|
|
};
|
|
};
|
|
const noop1 = createNoop();
|
|
const noop2 = createNoop();
|
|
const noop3 = createNoop();
|
|
const noop4 = createNoop();
|
|
const noop5 = createNoop();
|
|
|
|
const parent = {
|
|
parentSpied1: noop1
|
|
};
|
|
const target = Object.create(parent);
|
|
Object.defineProperty(target, 'spied1', {
|
|
value: noop1,
|
|
writable: true,
|
|
configurable: true,
|
|
enumerable: true
|
|
});
|
|
Object.defineProperty(target, 'spied2', {
|
|
value: noop2,
|
|
writable: true,
|
|
configurable: true,
|
|
enumerable: true
|
|
});
|
|
let _spied3 = noop3;
|
|
Object.defineProperty(target, 'spied3', {
|
|
configurable: true,
|
|
set: function(val) {
|
|
_spied3 = val;
|
|
},
|
|
get: function() {
|
|
return _spied3;
|
|
},
|
|
enumerable: true
|
|
});
|
|
target.spied4 = noop4;
|
|
Object.defineProperty(target, 'notSpied2', {
|
|
value: noop2,
|
|
writable: false,
|
|
configurable: true,
|
|
enumerable: true
|
|
});
|
|
Object.defineProperty(target, 'notSpied3', {
|
|
value: noop3,
|
|
writable: true,
|
|
configurable: false,
|
|
enumerable: true
|
|
});
|
|
Object.defineProperty(target, 'notSpied4', {
|
|
configurable: false,
|
|
set: function() {
|
|
/**/
|
|
},
|
|
get: function() {
|
|
return noop4;
|
|
},
|
|
enumerable: true
|
|
});
|
|
Object.defineProperty(target, 'notSpied5', {
|
|
value: noop5,
|
|
writable: true,
|
|
configurable: true,
|
|
enumerable: false
|
|
});
|
|
target.notSpied6 = 6;
|
|
|
|
const spiedObject = spyRegistry.spyOnAllFunctions(target);
|
|
|
|
expect(target.parentSpied1).toBe('I am a spy');
|
|
expect(target.notSpied2).toBe(noop2);
|
|
expect(target.notSpied3).toBe(noop3);
|
|
expect(target.notSpied4).toBe(noop4);
|
|
expect(target.notSpied5).toBe(noop5);
|
|
expect(target.notSpied6).toBe(6);
|
|
expect(target.spied1).toBe('I am a spy');
|
|
expect(target.spied2).toBe('I am a spy');
|
|
expect(target.spied3).toBe('I am a spy');
|
|
expect(target.spied4).toBe('I am a spy');
|
|
expect(spiedObject).toBe(target);
|
|
});
|
|
|
|
it('overrides prototype methods on the object', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
createSpy: function() {
|
|
return 'I am a spy';
|
|
}
|
|
});
|
|
|
|
const noop1 = function() {};
|
|
const noop2 = function() {};
|
|
|
|
const MyClass = function() {
|
|
this.spied1 = noop1;
|
|
};
|
|
MyClass.prototype.spied2 = noop2;
|
|
|
|
const target = new MyClass();
|
|
spyRegistry.spyOnAllFunctions(target);
|
|
|
|
expect(target.spied1).toBe('I am a spy');
|
|
expect(target.spied2).toBe('I am a spy');
|
|
expect(MyClass.prototype.spied2).toBe(noop2);
|
|
});
|
|
|
|
it('does not override non-enumerable properties (like Object.prototype methods)', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
createSpy: function() {
|
|
return 'I am a spy';
|
|
}
|
|
});
|
|
const target = {
|
|
spied1: function() {}
|
|
};
|
|
|
|
spyRegistry.spyOnAllFunctions(target);
|
|
|
|
expect(target.spied1).toBe('I am a spy');
|
|
expect(target.toString).not.toBe('I am a spy');
|
|
expect(target.hasOwnProperty).not.toBe('I am a spy');
|
|
});
|
|
describe('when includeNonEnumerable is true', function() {
|
|
it('does not override Object.prototype methods', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
createSpy: function() {
|
|
return 'I am a spy';
|
|
}
|
|
});
|
|
const target = {
|
|
spied1: function() {}
|
|
};
|
|
|
|
spyRegistry.spyOnAllFunctions(target, true);
|
|
|
|
expect(target.spied1).toBe('I am a spy');
|
|
expect(target.toString).not.toBe('I am a spy');
|
|
expect(target.hasOwnProperty).not.toBe('I am a spy');
|
|
});
|
|
|
|
it('overrides non-enumerable properties', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
createSpy: function() {
|
|
return 'I am a spy';
|
|
}
|
|
});
|
|
const target = {
|
|
spied1: function() {},
|
|
spied2: function() {}
|
|
};
|
|
|
|
Object.defineProperty(target, 'spied2', {
|
|
enumerable: false,
|
|
writable: true,
|
|
configurable: true
|
|
});
|
|
|
|
spyRegistry.spyOnAllFunctions(target, true);
|
|
|
|
expect(target.spied1).toBe('I am a spy');
|
|
expect(target.spied2).toBe('I am a spy');
|
|
});
|
|
|
|
it('should not spy on non-enumerable functions named constructor', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
createSpy: function() {
|
|
return 'I am a spy';
|
|
}
|
|
});
|
|
const target = {
|
|
constructor: function() {}
|
|
};
|
|
|
|
Object.defineProperty(target, 'constructor', {
|
|
enumerable: false,
|
|
writable: true,
|
|
configurable: true
|
|
});
|
|
|
|
spyRegistry.spyOnAllFunctions(target, true);
|
|
|
|
expect(target.constructor).not.toBe('I am a spy');
|
|
});
|
|
|
|
it('should spy on enumerable functions named constructor', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
createSpy: function() {
|
|
return 'I am a spy';
|
|
}
|
|
});
|
|
const target = {
|
|
constructor: function() {}
|
|
};
|
|
|
|
spyRegistry.spyOnAllFunctions(target, true);
|
|
|
|
expect(target.constructor).toBe('I am a spy');
|
|
});
|
|
|
|
it('should not throw an exception if we try and access strict mode restricted properties', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
createSpy: function() {
|
|
return 'I am a spy';
|
|
}
|
|
});
|
|
const target = function() {};
|
|
const fn = function() {
|
|
spyRegistry.spyOnAllFunctions(target, true);
|
|
};
|
|
|
|
expect(fn).not.toThrow();
|
|
});
|
|
|
|
it('should not spy on properties which are more permissable further up the prototype chain', function() {
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
createSpy: function() {
|
|
return 'I am a spy';
|
|
}
|
|
});
|
|
const targetParent = Object.defineProperty({}, 'sharedProp', {
|
|
value: function() {},
|
|
writable: true,
|
|
configurable: true
|
|
});
|
|
|
|
const target = Object.create(targetParent);
|
|
|
|
Object.defineProperty(target, 'sharedProp', {
|
|
value: function() {}
|
|
});
|
|
|
|
const fn = function() {
|
|
spyRegistry.spyOnAllFunctions(target, true);
|
|
};
|
|
|
|
expect(fn).not.toThrow();
|
|
expect(target).not.toBe('I am a spy');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('#clearSpies', function() {
|
|
it('restores the original functions on the spied-upon objects', function() {
|
|
const spies = [];
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
currentSpies: function() {
|
|
return spies;
|
|
},
|
|
createSpy: createSpy
|
|
});
|
|
const originalFunction = function() {};
|
|
const target = { spiedFunc: originalFunction };
|
|
|
|
spyRegistry.spyOn(target, 'spiedFunc');
|
|
spyRegistry.clearSpies();
|
|
|
|
expect(target.spiedFunc).toBe(originalFunction);
|
|
});
|
|
|
|
it('restores the original functions, even when that spy has been replace and re-spied upon', function() {
|
|
const spies = [];
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
currentSpies: function() {
|
|
return spies;
|
|
},
|
|
createSpy: createSpy
|
|
});
|
|
const originalFunction = function() {};
|
|
const target = { spiedFunc: originalFunction };
|
|
|
|
spyRegistry.spyOn(target, 'spiedFunc');
|
|
|
|
// replace the original spy with some other function
|
|
target.spiedFunc = function() {};
|
|
|
|
// spy on the function in that location again
|
|
spyRegistry.spyOn(target, 'spiedFunc');
|
|
|
|
spyRegistry.clearSpies();
|
|
|
|
expect(target.spiedFunc).toBe(originalFunction);
|
|
});
|
|
|
|
it("does not add a property that the spied-upon object didn't originally have", function() {
|
|
const spies = [];
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
currentSpies: function() {
|
|
return spies;
|
|
},
|
|
createSpy: createSpy
|
|
});
|
|
const originalFunction = function() {};
|
|
const targetParent = { spiedFunc: originalFunction };
|
|
|
|
const target = Object.create(targetParent);
|
|
|
|
expect(target.hasOwnProperty('spiedFunc')).toBe(false);
|
|
|
|
spyRegistry.spyOn(target, 'spiedFunc');
|
|
spyRegistry.clearSpies();
|
|
|
|
expect(target.hasOwnProperty('spiedFunc')).toBe(false);
|
|
expect(target.spiedFunc).toBe(originalFunction);
|
|
});
|
|
|
|
it("restores the original function when it's inherited and cannot be deleted", function() {
|
|
const spies = [];
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
currentSpies: function() {
|
|
return spies;
|
|
},
|
|
createSpy: createSpy
|
|
});
|
|
const originalFunction = function() {};
|
|
const targetParent = { spiedFunc: originalFunction };
|
|
|
|
const target = Object.create(targetParent);
|
|
|
|
spyRegistry.spyOn(target, 'spiedFunc');
|
|
|
|
// simulate a spy that cannot be deleted
|
|
Object.defineProperty(target, 'spiedFunc', {
|
|
configurable: false
|
|
});
|
|
|
|
spyRegistry.clearSpies();
|
|
|
|
expect(jasmineUnderTest.isSpy(target.spiedFunc)).toBe(false);
|
|
});
|
|
|
|
it('restores window.onerror by overwriting, not deleting', function() {
|
|
function FakeWindow() {}
|
|
FakeWindow.prototype.onerror = function() {};
|
|
|
|
const spies = [];
|
|
const global = new FakeWindow();
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
currentSpies: function() {
|
|
return spies;
|
|
},
|
|
createSpy: createSpy,
|
|
global: global
|
|
});
|
|
|
|
spyRegistry.spyOn(global, 'onerror');
|
|
spyRegistry.clearSpies();
|
|
expect(global.onerror).toBe(FakeWindow.prototype.onerror);
|
|
expect(global.hasOwnProperty('onerror')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('spying on properties', function() {
|
|
it('restores the original properties on the spied-upon objects', function() {
|
|
const spies = [];
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
currentSpies: function() {
|
|
return spies;
|
|
},
|
|
createSpy: createSpy
|
|
});
|
|
const originalReturn = 1;
|
|
const target = {};
|
|
|
|
Object.defineProperty(target, 'spiedProp', {
|
|
get: function() {
|
|
return originalReturn;
|
|
},
|
|
configurable: true
|
|
});
|
|
|
|
spyRegistry.spyOnProperty(target, 'spiedProp');
|
|
spyRegistry.clearSpies();
|
|
|
|
expect(target.spiedProp).toBe(originalReturn);
|
|
});
|
|
|
|
it("does not add a property that the spied-upon object didn't originally have", function() {
|
|
const spies = [];
|
|
const spyRegistry = new privateUnderTest.SpyRegistry({
|
|
currentSpies: function() {
|
|
return spies;
|
|
},
|
|
createSpy: createSpy
|
|
});
|
|
const originalReturn = 1;
|
|
const targetParent = {};
|
|
|
|
Object.defineProperty(targetParent, 'spiedProp', {
|
|
get: function() {
|
|
return originalReturn;
|
|
},
|
|
configurable: true
|
|
});
|
|
|
|
const target = Object.create(targetParent);
|
|
|
|
expect(target.hasOwnProperty('spiedProp')).toBe(false);
|
|
|
|
spyRegistry.spyOnProperty(target, 'spiedProp');
|
|
spyRegistry.clearSpies();
|
|
|
|
expect(target.hasOwnProperty('spiedProp')).toBe(false);
|
|
expect(target.spiedProp).toBe(originalReturn);
|
|
});
|
|
});
|
|
});
|