From d70a8ca930c967d44e8cf769909f30bdb39e6ae8 Mon Sep 17 00:00:00 2001 From: Viktor Rasevych Date: Thu, 2 Nov 2023 19:16:34 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=8B=20Implement=20setOrientationPropertie?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/criteo-mraid.js | 2 +- src/mraid.ts | 33 ++++ src/mraidapi.ts | 16 ++ src/mraidbridge/androidmraidbridge.ts | 15 ++ src/mraidbridge/iosmraidbridge.ts | 18 +++ src/mraidbridge/mraidbridge.ts | 5 + src/mraidbridge/sdkinteractor.ts | 10 ++ src/orientationproperties.ts | 71 ++++++++ tests/mraid.test.ts | 160 +++++++++++++++++++ tests/mraidbridge/androidmraidbridge.test.ts | 14 +- tests/mraidbridge/iosmraidbridge.test.ts | 21 +++ tests/mraidbridge/sdkinteractor.test.ts | 9 ++ tests/orientationproperties.test.ts | 21 +++ 13 files changed, 393 insertions(+), 2 deletions(-) create mode 100644 src/orientationproperties.ts create mode 100644 tests/orientationproperties.test.ts diff --git a/build/criteo-mraid.js b/build/criteo-mraid.js index 5fd61ec..c704163 100644 --- a/build/criteo-mraid.js +++ b/build/criteo-mraid.js @@ -1 +1 @@ -!function(){"use strict";function isFunction(any){return"function"==typeof any}function isNumber(any){return"number"==typeof any}!function(LogLevel){LogLevel.Debug="Debug",LogLevel.Info="Info",LogLevel.Warning="Warning",LogLevel.Error="Error"}(LogLevel=LogLevel||{}),function(MraidEvent){MraidEvent.Ready="ready",MraidEvent.Error="error",MraidEvent.StateChange="stateChange",MraidEvent.ViewableChange="viewableChange",MraidEvent.SizeChange="sizeChange"}(MraidEvent=MraidEvent||{});var LogLevel,MraidEvent,MraidState,MraidPlacementType,EventsCoordinator=function(){function EventsCoordinator(){this.eventListeners=new Map(Object.values(MraidEvent).map(function(e){return[e,new Set]}))}return EventsCoordinator.prototype.addEventListener=function(event,listener,logger){var _a;event&&this.isCorrectEvent(event)?listener?isFunction(listener)?null!=(_a=this.eventListeners.get(event))&&_a.add(listener):logger(LogLevel.Error,"addEventListener","Incorrect listener when addEventListener. \n Listener is not a function. Actual type = ".concat(typeof listener)):logger(LogLevel.Error,"addEventListener","Incorrect listener when addEventListener. It is null or undefined"):logger(LogLevel.Error,"addEventListener","Incorrect event when addEventListener.Type = ".concat(typeof event,", value = ").concat(event))},EventsCoordinator.prototype.removeEventListener=function(event,listener,logger){var listeners;event&&this.isCorrectEvent(event)?listener&&!isFunction(listener)?logger(LogLevel.Error,"removeEventListener","Incorrect listener when removeEventListener. \n Listener is not a function. Actual type = ".concat(typeof listener)):(listeners=this.eventListeners.get(event),listener?null!=listeners&&listeners.delete(listener):null!=listeners&&listeners.clear()):logger(LogLevel.Error,"removeEventListener","Incorrect event when removeEventListener.Type = ".concat(typeof event,", value = ").concat(event))},EventsCoordinator.prototype.fireReadyEvent=function(){var _a;null!=(_a=this.eventListeners.get(MraidEvent.Ready))&&_a.forEach(function(value){null!=value&&value()})},EventsCoordinator.prototype.fireErrorEvent=function(message,action){var _a;null!=(_a=this.eventListeners.get(MraidEvent.Error))&&_a.forEach(function(value){null!=value&&value(message,action)})},EventsCoordinator.prototype.fireStateChangeEvent=function(newState){var _a;null!=(_a=this.eventListeners.get(MraidEvent.StateChange))&&_a.forEach(function(value){null!=value&&value(newState)})},EventsCoordinator.prototype.fireViewableChangeEvent=function(isViewable){var _a;null!=(_a=this.eventListeners.get(MraidEvent.ViewableChange))&&_a.forEach(function(value){null!=value&&value(isViewable)})},EventsCoordinator.prototype.fireSizeChangeEvent=function(width,height){var _a;null!=(_a=this.eventListeners.get(MraidEvent.SizeChange))&&_a.forEach(function(value){null!=value&&value(width,height)})},EventsCoordinator.prototype.isCorrectEvent=function(event){return event&&this.eventListeners.has(event)},EventsCoordinator}(),ExpandProperties=(!function(MraidState){MraidState.Loading="loading",MraidState.Default="default",MraidState.Expanded="expanded",MraidState.Hidden="hidden",MraidState.Resized="resized"}(MraidState=MraidState||{}),!function(MraidPlacementType){MraidPlacementType.Unknown="",MraidPlacementType.Inline="inline",MraidPlacementType.Interstitial="interstitial"}(MraidPlacementType=MraidPlacementType||{}),function(width,height){this.useCustomClose=!1,this.isModal=!0,this.width=width,this.height=height});var SdkFeature,Size=function(){function Size(width,height){this.width=width,this.height=height}return Size.prototype.clone=function(){return new Size(this.width,this.height)},Size}();!function(SdkFeature){SdkFeature.Sms="sms",SdkFeature.Tel="tel",SdkFeature.Calendar="calendar",SdkFeature.StorePicture="storePicture",SdkFeature.InlineVideo="inlineVideo"}(SdkFeature=SdkFeature||{});var ClosePosition,defaultSupportedSdkFeatures=new function(sms,tel,inlineVideo){this.calendar=!1,this.storePicture=!1,this.sms=sms,this.tel=tel,this.inlineVideo=inlineVideo}(!1,!1,!1),Position=function(){function Position(x,y,width,height){this.x=x,this.y=y,this.width=width,this.height=height}return Position.prototype.clone=function(){return new Position(this.x,this.y,this.width,this.height)},Position}(),initialPosition=new Position(0,0,0,0),ResizeProperties=(!function(ClosePosition){ClosePosition.TopLeft="top-left",ClosePosition.TopRight="top-right",ClosePosition.Center="center",ClosePosition.BottomLeft="bottom-left",ClosePosition.BottomRight="bottom-right",ClosePosition.TopCenter="top-center",ClosePosition.BottomCenter="bottom-center"}(ClosePosition=ClosePosition||{}),function(){function ResizeProperties(width,height,offsetX,offsetY,customClosePosition,allowOffscreen){void 0===customClosePosition&&(customClosePosition=ClosePosition.TopRight),void 0===allowOffscreen&&(allowOffscreen=!0),this.width=width,this.height=height,this.offsetX=offsetX,this.offsetY=offsetY,this.customClosePosition=customClosePosition,this.allowOffscreen=allowOffscreen}return ResizeProperties.prototype.copy=function(){return new ResizeProperties(this.width,this.height,this.offsetX,this.offsetY,this.customClosePosition,this.allowOffscreen)},ResizeProperties}()),ResizePropertiesValidator=function(){function ResizePropertiesValidator(){this.closeRegionSize=50,this.halfCloseRegionSize=this.closeRegionSize/2,this.adMinSize=this.closeRegionSize}return ResizePropertiesValidator.prototype.validate=function(resizeProperties,maxSize,currentPosition){return resizeProperties?0===Object.keys(resizeProperties).length?"Resize properties object is empty":this.validateSize(resizeProperties.width,"width",maxSize.width)||this.validateSize(resizeProperties.height,"height",maxSize.height)||this.validateOffset(resizeProperties.offsetX,"offsetX")||this.validateOffset(resizeProperties.offsetY,"offsetY")||this.validateCustomClosePosition(resizeProperties.customClosePosition)||this.validateAllowOffscreen(resizeProperties.allowOffscreen)||this.validateCloseButtonPosition(resizeProperties,maxSize,currentPosition):"Resize properties object is not passed"},ResizePropertiesValidator.prototype.joinedClosePosition=function(){return"[".concat(Object.values(ClosePosition).join(", "),"]")},ResizePropertiesValidator.prototype.validateSize=function(value,side,maxSize){return value||0===value?isNumber(value)&&Number.isFinite(value)?value { + bridge.setOrientationProperties(allowOrientationChange, forceOrientation); + }); + } + private callForAll(lambda: (mraidBridge: MraidBridge) => void) { this.bridges.forEach((bridge) => { lambda(bridge); diff --git a/src/orientationproperties.ts b/src/orientationproperties.ts new file mode 100644 index 0000000..0ecdd31 --- /dev/null +++ b/src/orientationproperties.ts @@ -0,0 +1,71 @@ +import { Anything } from "./utils"; +import { ExpandProperties } from "./expand"; + +export enum Orientation { + Portrait = "portrait", + Landscape = "landscape", + None = "none", +} + +export class OrientationProperties { + allowOrientationChange: boolean | Anything; + + forceOrientation: Orientation | Anything; + + constructor(allowOrientationChange: boolean, forceOrientation: Orientation) { + this.allowOrientationChange = allowOrientationChange; + this.forceOrientation = forceOrientation; + } + + clone(): OrientationProperties { + return new OrientationProperties( + this.allowOrientationChange, + this.forceOrientation + ); + } +} + +export const defaultOrientationProperties = new OrientationProperties( + true, + Orientation.None +); + +export function joinedOrientations(): string { + return `[${Object.values(Orientation).join(", ")}]`; +} + +export function validateOrientationProperties( + orientationProperties: OrientationProperties | Anything +): string | null { + if (!orientationProperties) { + return "Orientation properties object is not passed"; + } + if (Object.keys(orientationProperties).length === 0) { + return "Orientation properties object is empty"; + } + let hasAnyProperty = false; + Object.keys(defaultOrientationProperties).forEach((property) => { + if (Object.prototype.hasOwnProperty.call(orientationProperties, property)) { + hasAnyProperty = true; + } + }); + if (!hasAnyProperty) { + return "The orientation properties object does not contain the 'allowOrientationChange' or 'forceOrientation' properties"; + } + + const { allowOrientationChange, forceOrientation } = orientationProperties; + + if (allowOrientationChange) { + if (typeof allowOrientationChange !== "boolean") { + return "'allowOrientationChange' should be boolean"; + } + } + + if (forceOrientation) { + if (Object.values(Orientation).includes(forceOrientation)) { + return null; + } + return `'forceOrientation' should be one of ${joinedOrientations()}`; + } + return null; +} diff --git a/tests/mraid.test.ts b/tests/mraid.test.ts index d5ceb18..13d8870 100644 --- a/tests/mraid.test.ts +++ b/tests/mraid.test.ts @@ -18,6 +18,10 @@ import { Logger } from "../src/log/logger"; import { SdkFeature } from "../src/sdkfeature"; import { initialPosition, Position } from "../src/position"; import { ClosePosition, ResizePropertiesValidator } from "../src/resize"; +import { + defaultOrientationProperties, + Orientation, +} from "../src/orientationproperties"; let mraid: MRAIDImplementation; let eventsCoordinator: EventsCoordinator; @@ -885,3 +889,159 @@ describe("when notifyResized", () => { ).once(); }); }); + +describe("when setOrientationProperties", () => { + test("given undefined orientation properties should log proper error message", () => { + mraid.setOrientationProperties(undefined); + verify( + logger.log( + LogLevel.Error, + "setOrientationProperties", + "Orientation properties object is not passed" + ) + ).once(); + verify( + sdkInteractor.setOrientationProperties(anything(), anything()) + ).never(); + }); + + test("given null orientation properties should log proper error message", () => { + mraid.setOrientationProperties(null); + verify( + logger.log( + LogLevel.Error, + "setOrientationProperties", + "Orientation properties object is not passed" + ) + ).once(); + verify( + sdkInteractor.setOrientationProperties(anything(), anything()) + ).never(); + }); + + test("given empty orientation properties should log proper error message", () => { + mraid.setOrientationProperties({}); + verify( + logger.log( + LogLevel.Error, + "setOrientationProperties", + "Orientation properties object is empty" + ) + ).once(); + verify( + sdkInteractor.setOrientationProperties(anything(), anything()) + ).never(); + }); + + test("given orientation properties with wrong fields should log proper error message", () => { + mraid.setOrientationProperties({ customProp: true }); + verify( + logger.log( + LogLevel.Error, + "setOrientationProperties", + "The orientation properties object does not contain the 'allowOrientationChange' or 'forceOrientation' properties" + ) + ).once(); + verify( + sdkInteractor.setOrientationProperties(anything(), anything()) + ).never(); + }); + + it.each([Infinity, "123", new Set(), 123])( + "given %p allowOrientationChange should log proper error message", + (param) => { + mraid.setOrientationProperties({ allowOrientationChange: param }); + + verify( + logger.log( + LogLevel.Error, + "setOrientationProperties", + "'allowOrientationChange' should be boolean" + ) + ).once(); + verify( + sdkInteractor.setOrientationProperties(anything(), anything()) + ).never(); + } + ); + + it.each([Infinity, "123", new Set(), 123])( + "given %p forceOrientation should log proper error message", + (param) => { + mraid.setOrientationProperties({ forceOrientation: param }); + + verify( + logger.log( + LogLevel.Error, + "setOrientationProperties", + "'forceOrientation' should be one of [portrait, landscape, none]" + ) + ).once(); + verify( + sdkInteractor.setOrientationProperties(anything(), anything()) + ).never(); + } + ); + + it.each([true, false])( + "given %p allowOrientationChange should return same value when getOrientationProperties", + (param) => { + mraid.setOrientationProperties({ allowOrientationChange: param }); + + const orientationProperties = mraid.getOrientationProperties(); + expect(orientationProperties.allowOrientationChange).toBe(param); + expect(orientationProperties.forceOrientation).toBe( + defaultOrientationProperties.forceOrientation + ); + verify( + sdkInteractor.setOrientationProperties( + orientationProperties.allowOrientationChange, + orientationProperties.forceOrientation + ) + ).once(); + } + ); + + it.each([Orientation.Landscape, Orientation.Portrait, Orientation.None])( + "given %p forceOrientation should return same value when getOrientationProperties", + (param) => { + mraid.setOrientationProperties({ forceOrientation: param }); + + const orientationProperties = mraid.getOrientationProperties(); + expect(orientationProperties.forceOrientation).toBe(param); + expect(orientationProperties.allowOrientationChange).toBe( + defaultOrientationProperties.allowOrientationChange + ); + verify( + sdkInteractor.setOrientationProperties( + orientationProperties.allowOrientationChange, + orientationProperties.forceOrientation + ) + ).once(); + } + ); + + test("given both properties are set should return same values", () => { + mraid.setOrientationProperties({ + allowOrientationChange: false, + forceOrientation: "portrait", + }); + + const orientationProperties = mraid.getOrientationProperties(); + expect(orientationProperties.allowOrientationChange).toBe(false); + expect(orientationProperties.forceOrientation).toBe(Orientation.Portrait); + verify( + sdkInteractor.setOrientationProperties( + orientationProperties.allowOrientationChange, + orientationProperties.forceOrientation + ) + ).once(); + }); +}); + +test("when getOrientationProperties should return proper default values", () => { + const orientationProperties = mraid.getOrientationProperties(); + + expect(orientationProperties.allowOrientationChange).toBe(true); + expect(orientationProperties.forceOrientation).toBe(Orientation.None); +}); diff --git a/tests/mraidbridge/androidmraidbridge.test.ts b/tests/mraidbridge/androidmraidbridge.test.ts index 60cab54..ad17434 100644 --- a/tests/mraidbridge/androidmraidbridge.test.ts +++ b/tests/mraidbridge/androidmraidbridge.test.ts @@ -1,10 +1,14 @@ import { instance, mock, verify } from "ts-mockito"; import { - CriteoInterface, AndroidMraidBridge, + CriteoInterface, } from "../../src/mraidbridge/androidmraidbridge"; import { LogLevel } from "../../src/log/loglevel"; import { ClosePosition } from "../../src/resize"; +import { + Orientation, + OrientationProperties, +} from "../../src/orientationproperties"; let androidMraidBridge: AndroidMraidBridge; let androidBridge: CriteoInterface; @@ -82,3 +86,11 @@ test("when call resize should delegate to criteoMraidBridge on window", () => { ) ).once(); }); + +test("when call setOrientationProperties should delegate to criteoMraidBridge on window", () => { + androidMraidBridge.setOrientationProperties(false, Orientation.Landscape); + + verify( + androidBridge.setOrientationProperties(false, Orientation.Landscape) + ).once(); +}); diff --git a/tests/mraidbridge/iosmraidbridge.test.ts b/tests/mraidbridge/iosmraidbridge.test.ts index 04cb7de..7e26910 100644 --- a/tests/mraidbridge/iosmraidbridge.test.ts +++ b/tests/mraidbridge/iosmraidbridge.test.ts @@ -11,9 +11,11 @@ import { OpenIosMessage, PlayVideoIosMessage, ResizeIosMessage, + SetOrientationPropertiesMessage, Webkit, } from "../../src/mraidbridge/iosmraidbridge"; import { ClosePosition } from "../../src/resize"; +import { Orientation } from "../../src/orientationproperties"; let iosMraidBridge: IosMraidBridge; let iosMessageHandler: CriteoMessageHandler; @@ -139,3 +141,22 @@ test("when call resize should delegate to criteoMraidBridge on window", () => { const capturedMessage = captureLastMessage(); expect(capturedMessage).toStrictEqual(expectedMessage); }); + +test("when call setOrientationProperties should delegate to criteoMraidBridge on window", () => { + const allowOrientationChange = false; + const forceOrientation = Orientation.None; + + const expectedMessage: SetOrientationPropertiesMessage = { + action: "set_orientation_properties", + allowOrientationChange, + forceOrientation, + }; + + iosMraidBridge.setOrientationProperties( + allowOrientationChange, + forceOrientation + ); + + const capturedMessage = captureLastMessage(); + expect(capturedMessage).toStrictEqual(expectedMessage); +}); diff --git a/tests/mraidbridge/sdkinteractor.test.ts b/tests/mraidbridge/sdkinteractor.test.ts index 716d4ff..9a1390d 100644 --- a/tests/mraidbridge/sdkinteractor.test.ts +++ b/tests/mraidbridge/sdkinteractor.test.ts @@ -3,6 +3,7 @@ import { SdkInteractor } from "../../src/mraidbridge/sdkinteractor"; import { MraidBridge } from "../../src/mraidbridge/mraidbridge"; import { LogLevel } from "../../src/log/loglevel"; import { ClosePosition } from "../../src/resize"; +import { Orientation } from "../../src/orientationproperties"; let sdkInteractor: SdkInteractor; let mraidBridges: MraidBridge[]; @@ -85,3 +86,11 @@ test("when call resize should delegate to every MraidBridge object", () => { ).once() ); }); + +test("when call setOrientationProperties should delegate to every MraidBridge object", () => { + sdkInteractor.setOrientationProperties(false, Orientation.Portrait); + + mraidBridges.forEach((bridge) => + verify(bridge.setOrientationProperties(false, Orientation.Portrait)).once() + ); +}); diff --git a/tests/orientationproperties.test.ts b/tests/orientationproperties.test.ts new file mode 100644 index 0000000..f2ebba8 --- /dev/null +++ b/tests/orientationproperties.test.ts @@ -0,0 +1,21 @@ +import { + joinedOrientations, + Orientation, + OrientationProperties, +} from "../src/orientationproperties"; + +test("when clone orientation properties should have the same values", () => { + const orientationProperties = new OrientationProperties( + true, + Orientation.Landscape + ); + + const clonedOrientationProperties = orientationProperties.clone(); + + expect(orientationProperties).toEqual(clonedOrientationProperties); +}); + +test("when joinedOrientations() should return correct joined string", () => { + const orientationsString = joinedOrientations(); + expect(orientationsString).toBe("[portrait, landscape, none]"); +});