From 0eb78b7096fe5a0f9187800420dc1ce2d75ba111 Mon Sep 17 00:00:00 2001 From: Abban Dunne Date: Tue, 15 Oct 2024 16:22:44 +0200 Subject: [PATCH] Implement VAR for C24_WMDE_Mobile_EN_01 - Add clickable headline that shows Use of Funds - Add colours for light and dark mode - Add test Ticket: https://phabricator.wikimedia.org/T376794 --- .../C24_WMDE_Mobile_EN_01/banner_var.ts | 4 +- .../components/BannerVar.vue | 230 ++++++++++++++++++ .../components/MiniBannerVar.vue | 48 ++++ .../C24_WMDE_Mobile_EN_01/event_map.ts | 5 +- .../styles/MiniBannerVar.scss | 160 ++++++++++++ .../styles/styles_var.scss | 25 ++ src/tracking/events/UseOfFundsShownEvent.ts | 14 ++ .../components/BannerVar.spec.ts | 176 ++++++++++++++ 8 files changed, 659 insertions(+), 3 deletions(-) create mode 100644 banners/mobile_english/C24_WMDE_Mobile_EN_01/components/BannerVar.vue create mode 100644 banners/mobile_english/C24_WMDE_Mobile_EN_01/components/MiniBannerVar.vue create mode 100644 banners/mobile_english/C24_WMDE_Mobile_EN_01/styles/MiniBannerVar.scss create mode 100644 banners/mobile_english/C24_WMDE_Mobile_EN_01/styles/styles_var.scss create mode 100644 src/tracking/events/UseOfFundsShownEvent.ts create mode 100644 test/banners/mobile_english/components/BannerVar.spec.ts diff --git a/banners/mobile_english/C24_WMDE_Mobile_EN_01/banner_var.ts b/banners/mobile_english/C24_WMDE_Mobile_EN_01/banner_var.ts index 416e5f204..51cf7c779 100644 --- a/banners/mobile_english/C24_WMDE_Mobile_EN_01/banner_var.ts +++ b/banners/mobile_english/C24_WMDE_Mobile_EN_01/banner_var.ts @@ -1,9 +1,9 @@ import { createVueApp } from '@src/createVueApp'; -import './styles/styles.scss'; +import './styles/styles_var.scss'; import BannerConductor from '@src/components/BannerConductor/BannerConductor.vue'; -import Banner from './components/BannerCtrl.vue'; +import Banner from './components/BannerVar.vue'; import { UrlRuntimeEnvironment } from '@src/utils/RuntimeEnvironment'; import { WindowResizeHandler } from '@src/utils/ResizeHandler'; import PageWPORG from '@src/page/PageWPORG'; diff --git a/banners/mobile_english/C24_WMDE_Mobile_EN_01/components/BannerVar.vue b/banners/mobile_english/C24_WMDE_Mobile_EN_01/components/BannerVar.vue new file mode 100644 index 000000000..0cc2618d2 --- /dev/null +++ b/banners/mobile_english/C24_WMDE_Mobile_EN_01/components/BannerVar.vue @@ -0,0 +1,230 @@ + + + diff --git a/banners/mobile_english/C24_WMDE_Mobile_EN_01/components/MiniBannerVar.vue b/banners/mobile_english/C24_WMDE_Mobile_EN_01/components/MiniBannerVar.vue new file mode 100644 index 000000000..608d134a9 --- /dev/null +++ b/banners/mobile_english/C24_WMDE_Mobile_EN_01/components/MiniBannerVar.vue @@ -0,0 +1,48 @@ + + + diff --git a/banners/mobile_english/C24_WMDE_Mobile_EN_01/event_map.ts b/banners/mobile_english/C24_WMDE_Mobile_EN_01/event_map.ts index 7bb6ae360..d2752a525 100644 --- a/banners/mobile_english/C24_WMDE_Mobile_EN_01/event_map.ts +++ b/banners/mobile_english/C24_WMDE_Mobile_EN_01/event_map.ts @@ -10,6 +10,7 @@ import { mapNotShownEvent } from '@src/tracking/LegacyEventTracking/mapNotShownE import { BannerSubmitEvent } from '@src/tracking/events/BannerSubmitEvent'; import { WMDESizeIssueEvent } from '@src/tracking/WPORG/WMDEBannerSizeIssue'; import { createViewportInfo } from '@src/tracking/LegacyEventTracking/createViewportInfo'; +import { UseOfFundsShownEvent } from '@src/tracking/events/UseOfFundsShownEvent'; export default new Map( [ [ CloseEvent.EVENT_NAME, mapCloseEvent ], @@ -26,5 +27,7 @@ export default new Map( [ default: return new WMDESizeIssueEvent( `submit`, createViewportInfo(), 1 ); } - } ] + } ], + [ UseOfFundsShownEvent.EVENT_NAME, + ( e: UseOfFundsShownEvent ): WMDELegacyBannerEvent => new WMDELegacyBannerEvent( e.eventName + ( e.userChoice !== '' ? `-${e.userChoice}` : '' ), 1 ) ] ] ); diff --git a/banners/mobile_english/C24_WMDE_Mobile_EN_01/styles/MiniBannerVar.scss b/banners/mobile_english/C24_WMDE_Mobile_EN_01/styles/MiniBannerVar.scss new file mode 100644 index 000000000..bcc511865 --- /dev/null +++ b/banners/mobile_english/C24_WMDE_Mobile_EN_01/styles/MiniBannerVar.scss @@ -0,0 +1,160 @@ +@use '@src/themes/Yperala/variables/globals'; +@use '@src/themes/Yperala/variables/breakpoints'; +@use '@src/themes/Treedip/variables/fonts'; + +$height-small: 328px !default; +$height-large: 288px !default; + +$heading-color-dark: #9f9f9f; +$heading-color-light: #595959; + +.skin-theme-clientpref-night .wmde-banner { + --mini-banner-headline-color: #{$heading-color-dark}; +} + +.skin-theme-clientpref-day .wmde-banner { + --mini-banner-headline-color: #{$heading-color-light}; +} + +.skin-theme-clientpref-os .wmde-banner { + @media ( prefers-color-scheme: dark ) { + --mini-banner-headline-color: #{$heading-color-dark}; + } + + @media ( prefers-color-scheme: light ) { + --mini-banner-headline-color: #{$heading-color-light}; + } +} + +.wmde-banner { + &-mini { + padding: 5px 0; + min-height: $height-small; + display: flex; + + @include breakpoints.phone-m-up { + min-height: $height-large; + } + + &-headline { + color: var( --mini-banner-headline-color ); + cursor: pointer; + height: 32px; + background: none; + border: 0; + font-weight: bold; + margin: 0 0 7px; + padding: 0 20px; + + &-link { + text-decoration: underline; + } + } + + &-content { + position: relative; + cursor: pointer; + text-align: center; + box-sizing: border-box; + margin: 0 auto; + background: var( --mini-background ); + border: 8px solid var( --mini-border ); + box-shadow: var( --mini-box-shadow ); + border-radius: 15px; + width: 98%; + } + + &-close { + position: absolute; + top: 2px; + right: -2px; + z-index: 2; + height: 30px; + width: 30px; + display: flex; + justify-content: center; + align-items: center; + + &-button { + background: none; + border: 0; + } + + svg { + height: 20px; + width: 20px; + } + } + + &-banner-slideshow { + width: 100%; + flex: 1 0 auto; + } + + &-button-group { + display: flex; + justify-content: center; + } + + &-button, + &-button-preselect { + width: 50%; + height: 40px; + border: 0; + border-radius: 20px; + font-weight: bold; + color: var( --mini-button-color ); + margin: 0 16px; + font-size: 14px; + white-space: nowrap; + transition: background-color 200ms ease-in-out; + cursor: pointer; + + @media ( min-width: 370px ) { + font-size: 16px; + } + } + + &-button-preselect { + background: var( --mini-button-alt-background ); + + &:hover, + &:focus { + background: var( --mini-button-alt-background-hover ); + } + } + + &-button { + background: var( --mini-button-background ); + + &:hover, + &:focus { + background: var( --mini-button-background-hover ); + } + } + + .wmde-banner-progress-bar-text { + visibility: hidden; + } + + &-payment-icons { + display: flex; + justify-content: center; + width: 100%; + height: 45px; + padding: 10px 0; + text-align: center; + + svg { + display: inline-block; + height: 18px; + width: auto; + margin: 0 5px; + } + + .sms-icon { + height: 24px; + } + } + } +} diff --git a/banners/mobile_english/C24_WMDE_Mobile_EN_01/styles/styles_var.scss b/banners/mobile_english/C24_WMDE_Mobile_EN_01/styles/styles_var.scss new file mode 100644 index 000000000..d47870272 --- /dev/null +++ b/banners/mobile_english/C24_WMDE_Mobile_EN_01/styles/styles_var.scss @@ -0,0 +1,25 @@ +@use 'src/themes/Yperala/swatches/skin_minerva' with ( + $progress-bar: true, + $select-group-image-label: true, + $soft-close: true, + $sms-icon: true, + $upgrade-to-yearly: true +); +@use '@src/components/BannerConductor/banner-transition'; +@use '@src/themes/UseOfFunds/swatches/skin_minerva' as uof-minerva; +@use '@src/themes/UseOfFunds/UseOfFunds'; +@use '@src/themes/Yperala/defaults'; +@use 'Banner'; +@use 'MiniBannerVar'; +@use 'FullPageBanner'; +@use '@src/themes/Yperala/Slider/Slider'; +@use '@src/themes/Yperala/ProgressBar/ProgressBar'; +@use '@src/themes/Yperala/DonationForm/MultiStepDonation'; +@use '@src/themes/Yperala/DonationForm/Forms/UpgradeToYearlyButtonForm'; +@use '@src/themes/Yperala/DonationForm/SubComponents/SelectGroup'; +@use '@src/themes/Yperala/DonationForm/SubComponents/SelectGroupImageLabel'; +@use '@src/themes/Yperala/DonationForm/SubComponents/SelectCustomAmount'; +@use '@src/themes/Yperala/DonationForm/SubComponents/SmsBox'; +@use '@src/themes/Yperala/Footer/Footer'; +@use '@src/themes/Yperala/Footer/SelectionInput'; +@use '@src/themes/Yperala/SoftClose/SoftClose'; diff --git a/src/tracking/events/UseOfFundsShownEvent.ts b/src/tracking/events/UseOfFundsShownEvent.ts new file mode 100644 index 000000000..c02caa1ef --- /dev/null +++ b/src/tracking/events/UseOfFundsShownEvent.ts @@ -0,0 +1,14 @@ +import { TrackingEvent, TrackingFeatureName } from '@src/tracking/TrackingEvent'; + +export class UseOfFundsShownEvent implements TrackingEvent { + public static readonly EVENT_NAME = 'use-of-funds-shown'; + + public readonly eventName: string = UseOfFundsShownEvent.EVENT_NAME; + public readonly customData: void; + public readonly feature: TrackingFeatureName; + public readonly userChoice: string = ''; + + public constructor( feature: TrackingFeatureName ) { + this.feature = feature; + } +} diff --git a/test/banners/mobile_english/components/BannerVar.spec.ts b/test/banners/mobile_english/components/BannerVar.spec.ts new file mode 100644 index 000000000..a255aa156 --- /dev/null +++ b/test/banners/mobile_english/components/BannerVar.spec.ts @@ -0,0 +1,176 @@ +import { afterEach, beforeEach, describe, test, vi, it, expect } from 'vitest'; +import { mount, VueWrapper } from '@vue/test-utils'; +import Banner from '@banners/mobile_english/C24_WMDE_Mobile_EN_01/components/BannerVar.vue'; +import { BannerStates } from '@src/components/BannerConductor/StateMachine/BannerStates'; +import { PageScroller } from '@src/utils/PageScroller/PageScroller'; +import { useOfFundsContent } from '@test/banners/useOfFundsContent'; +import { newDynamicContent } from '@test/banners/dynamicCampaignContent'; +import { CurrencyEn } from '@src/utils/DynamicContent/formatters/CurrencyEn'; +import { formItems } from '@test/banners/formItems'; +import { softCloseFeatures } from '@test/features/SoftCloseMobile'; +import { useOfFundsFeatures, useOfFundsScrollFeatures } from '@test/features/UseOfFunds'; +import { miniBannerFeatures } from '@test/features/MiniBanner'; +import { donationFormFeatures } from '@test/features/forms/MainDonation_UpgradeToYearlyButton'; +import { useFormModel } from '@src/components/composables/useFormModel'; +import { resetFormModel } from '@test/resetFormModel'; +import { DynamicContent } from '@src/utils/DynamicContent/DynamicContent'; +import { fullPageBannerFeatures } from '@test/features/FullPageBanner'; +import { formActionSwitchFeatures } from '@test/features/form_action_switch/MainDonation_UpgradeToYearlyButton'; +import { Tracker } from '@src/tracking/Tracker'; +import { bannerContentAnimatedTextFeatures, bannerContentDateAndTimeFeatures } from '@test/features/BannerContent'; +import { UseOfFundsShownEvent } from '@src/tracking/events/UseOfFundsShownEvent'; + +let pageScroller: PageScroller; +let tracker: Tracker; +const formModel = useFormModel(); +const translator = ( key: string ): string => key; + +describe( 'BannerVar.vue', () => { + + let wrapper: VueWrapper; + beforeEach( () => { + resetFormModel( formModel ); + vi.useFakeTimers(); + + pageScroller = { + scrollIntoView: vi.fn(), + scrollToTop: vi.fn() + }; + + tracker = { + trackEvent: vi.fn() + }; + } ); + + const getWrapper = ( dynamicContent: DynamicContent = null ): VueWrapper => { + // attachTo the document body to fix an issue with Vue Test Utils where + // clicking a submit button in a form does not fire the submit event + wrapper = mount( Banner, { + attachTo: document.body, + props: { + bannerState: BannerStates.Pending, + useOfFundsContent, + pageScroller, + remainingImpressions: 10, + donationURL: 'https://spenden.wikimedia.de' + }, + global: { + mocks: { + $translate: translator + }, + provide: { + translator: { translate: translator }, + dynamicCampaignText: dynamicContent ?? newDynamicContent(), + formActions: { donateWithAddressAction: 'https://example.com/with-address', donateAnonymouslyAction: 'https://example.com/without-address' }, + currencyFormatter: new CurrencyEn(), + formItems, + tracker + } + } + } ); + + return wrapper; + }; + + afterEach( () => { + wrapper.unmount(); + vi.restoreAllMocks(); + vi.useRealTimers(); + } ); + + describe( 'Content', () => { + test.skip.each( [ + [ 'expectShowsAnimatedVisitorsVsDonorsSentenceInMessage' ], + [ 'expectShowsAnimatedVisitorsVsDonorsSentenceInSlideShow' ], + [ 'expectHidesAnimatedVisitorsVsDonorsSentenceInMessage' ], + [ 'expectHidesAnimatedVisitorsVsDonorsSentenceInSlideShow' ] + ] )( '%s', async ( testName: string ) => { + await bannerContentAnimatedTextFeatures[ testName ]( getWrapper ); + } ); + + test.each( [ + [ 'expectShowsLiveDateAndTimeInMiniBanner' ], + [ 'expectShowsLiveDateAndTimeInFullPageBanner' ] + ] )( '%s', async ( testName: string ) => { + await bannerContentDateAndTimeFeatures[ testName ]( getWrapper ); + } ); + } ); + + describe( 'Donation Form Happy Paths', () => { + test.each( [ + [ 'expectMainDonationFormSubmitsWhenSofortIsSelected' ], + [ 'expectMainDonationFormSubmitsWhenYearlyIsSelected' ], + [ 'expectMainDonationFormGoesToUpgrade' ], + [ 'expectUpgradeToYearlyFormSubmitsUpgrade' ], + [ 'expectUpgradeToYearlyFormSubmitsDontUpgrade' ] + ] )( '%s', async ( testName: string ) => { + await donationFormFeatures[ testName ]( getWrapper() ); + } ); + + test.each( [ + [ 'expectMainDonationFormSubmitsWithAddressForDirectDebit' ], + [ 'expectUpgradeToYearlyFormSubmitsWithAddressForDirectDebit' ] + ] )( '%s', async ( testName: string ) => { + await formActionSwitchFeatures[ testName ]( getWrapper() ); + } ); + } ); + + describe( 'Soft Close', () => { + test.skip.each( [ + [ 'expectShowsSoftCloseOnMiniBannerClose' ], + [ 'expectDoesNotShowSoftCloseOnFullBannerClose' ], + [ 'expectEmitsSoftCloseCloseEvent' ], + [ 'expectEmitsSoftCloseMaybeLaterEvent' ], + [ 'expectEmitsSoftCloseAlreadyDonatedEvent' ], + [ 'expectEmitsSoftCloseTimeOutEvent' ], + [ 'expectEmitsBannerContentChangedOnSoftClose' ], + [ 'expectDoesNotShowSoftCloseOnFinalBannerImpression' ] + ] )( '%s', async ( testName: string ) => { + await softCloseFeatures[ testName ]( getWrapper() ); + } ); + } ); + + describe( 'Use of Funds', () => { + test.each( [ + [ 'expectShowsUseOfFunds' ], + [ 'expectHidesUseOfFunds' ] + ] )( '%s', async ( testName: string ) => { + await useOfFundsFeatures[ testName ]( getWrapper() ); + } ); + + test.each( [ + [ 'expectScrollsToFormWhenCallToActionIsClicked' ], + [ 'expectScrollsToLinkWhenCloseIsClicked' ] + ] )( '%s', async ( testName: string ) => { + await useOfFundsScrollFeatures[ testName ]( getWrapper(), pageScroller ); + } ); + } ); + + describe( 'Mini Banner', () => { + test.each( [ + [ 'expectSlideShowPlaysWhenMiniBannerBecomesVisible' ], + [ 'expectSlideShowStopsWhenFullBannerBecomesVisible' ], + [ 'expectShowsFullPageWhenCallToActionIsClicked' ], + [ 'expectEmitsBannerContentChangedEventWhenCallToActionIsClicked' ] + ] )( '%s', async ( testName: string ) => { + await miniBannerFeatures[ testName ]( getWrapper() ); + } ); + + it( 'Shows the use of funds', async () => { + wrapper = getWrapper(); + + await wrapper.find( '.wmde-banner-mini-headline' ).trigger( 'click' ); + + expect( wrapper.find( '.banner-modal' ).classes() ).toContain( 'is-visible' ); + expect( tracker.trackEvent ).toBeCalledWith( new UseOfFundsShownEvent( 'MiniBanner' ) ); + } ); + } ); + + describe( 'Full Page Banner', () => { + test.each( [ + [ 'expectEmitsCloseEvent' ] + ] )( '%s', async ( testName: string ) => { + await fullPageBannerFeatures[ testName ]( getWrapper() ); + } ); + } ); +} );