From 6f01d14b56d76e13f6559875374f23f19c9cc9c2 Mon Sep 17 00:00:00 2001 From: Abban Dunne Date: Thu, 7 Nov 2024 13:01:21 +0100 Subject: [PATCH] Implement VAR for C24_WMDE_Desktop_DE_11 VAR has the double progress bar Ticket: https://phabricator.wikimedia.org/T379246 --- .../C24_WMDE_Desktop_DE_11/banner_var.ts | 8 +- .../components/BannerVar.vue | 165 ++++++++++++++++ .../C24_WMDE_Desktop_DE_11/messages.ts | 2 - .../C24_WMDE_Desktop_DE_11/messages_var.ts | 35 ++++ .../styles/MainBannerVar.scss | 53 +++++ .../styles/styles_var.scss | 56 ++++++ .../ProgressBar/DoubleProgressBar.vue | 19 +- .../messages/DoubleProgressBar.de.ts | 7 + .../ProgressBar/DoubleProgressBar.scss | 28 ++- .../ProgressBar/_transitionsDouble.scss | 19 +- src/themes/Svingle/swatches/color_dark.scss | 14 ++ src/themes/Svingle/swatches/color_light.scss | 15 ++ src/themes/Svingle/swatches/skin_default.scss | 2 + .../Svingle/swatches/skin_vector-2022.scss | 5 + .../DynamicContent/DynamicCampaignText.ts | 7 + src/utils/DynamicContent/DynamicContent.ts | 1 + .../generators/DaysPassedSentence.ts | 27 +++ .../components/BannerVar.spec.ts | 182 ++++++++++++++++++ test/banners/dynamicCampaignContent.ts | 1 + .../ProgressBar/DoubleProgressBar.spec.ts | 47 +++++ .../ProgressBar/ProgressBar.spec.ts | 1 + .../DynamicCampaignText.spec.ts | 4 + .../generators/DaysPassedSentence.spec.ts | 25 +++ 23 files changed, 692 insertions(+), 31 deletions(-) create mode 100644 banners/desktop/C24_WMDE_Desktop_DE_11/components/BannerVar.vue create mode 100644 banners/desktop/C24_WMDE_Desktop_DE_11/messages_var.ts create mode 100644 banners/desktop/C24_WMDE_Desktop_DE_11/styles/MainBannerVar.scss create mode 100644 banners/desktop/C24_WMDE_Desktop_DE_11/styles/styles_var.scss create mode 100644 src/components/ProgressBar/messages/DoubleProgressBar.de.ts create mode 100644 src/utils/DynamicContent/generators/DaysPassedSentence.ts create mode 100644 test/banners/desktop/C24_WMDE_Desktop_DE_11/components/BannerVar.spec.ts create mode 100644 test/unit/utils/DynamicContent/generators/DaysPassedSentence.spec.ts diff --git a/banners/desktop/C24_WMDE_Desktop_DE_11/banner_var.ts b/banners/desktop/C24_WMDE_Desktop_DE_11/banner_var.ts index 51ffcb4de..ba0d84d52 100644 --- a/banners/desktop/C24_WMDE_Desktop_DE_11/banner_var.ts +++ b/banners/desktop/C24_WMDE_Desktop_DE_11/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/FallbackBannerConductor.vue'; -import Banner from './components/BannerCtrl.vue'; +import Banner from './components/BannerVar.vue'; import FallbackBanner from './components/FallbackBanner.vue'; import { UrlRuntimeEnvironment } from '@src/utils/RuntimeEnvironment'; import { WindowResizeHandler } from '@src/utils/ResizeHandler'; @@ -21,12 +21,13 @@ import { createFallbackDonationURL } from '@src/createFallbackDonationURL'; import { LocalStorageCloseTracker } from '@src/utils/LocalCloseTracker'; // Locale-specific imports -import messages from './messages'; +import messages from './messages_var'; import { LocaleFactoryDe } from '@src/utils/LocaleFactory/LocaleFactoryDe'; // Channel specific form setup import { createFormItems } from './form_items'; import { createFormActions } from '@src/createFormActions'; +import { currentCampaignTimePercentage } from '@src/components/ProgressBar/currentCampaignTimePercentage'; const date = new Date(); const localeFactory = new LocaleFactoryDe(); @@ -72,5 +73,6 @@ app.provide( 'currencyFormatter', currencyFormatter ); app.provide( 'formItems', createFormItems( translator, currencyFormatter.euroAmount.bind( currencyFormatter ) ) ); app.provide( 'formActions', createFormActions( page.getTracking(), impressionCount, { afo: '1', ap: '0' } ) ); app.provide( 'tracker', tracker ); +app.provide( 'currentCampaignTimePercentage', currentCampaignTimePercentage( date, page.getCampaignParameters() ) ); app.mount( page.getBannerContainer() ); diff --git a/banners/desktop/C24_WMDE_Desktop_DE_11/components/BannerVar.vue b/banners/desktop/C24_WMDE_Desktop_DE_11/components/BannerVar.vue new file mode 100644 index 000000000..471918705 --- /dev/null +++ b/banners/desktop/C24_WMDE_Desktop_DE_11/components/BannerVar.vue @@ -0,0 +1,165 @@ + + + diff --git a/banners/desktop/C24_WMDE_Desktop_DE_11/messages.ts b/banners/desktop/C24_WMDE_Desktop_DE_11/messages.ts index 0afbded48..8bb2ea2d4 100644 --- a/banners/desktop/C24_WMDE_Desktop_DE_11/messages.ts +++ b/banners/desktop/C24_WMDE_Desktop_DE_11/messages.ts @@ -26,8 +26,6 @@ const messages: TranslationMessages = { 'upgrade-to-yearly-no': 'Nein, ich spende einmalig {{amount}}', 'upgrade-to-yearly-yes': 'Ja, ich spende {{amount}} jährlich', 'campaign-day-only-n-days': 'Heute sind es nur noch {{days}} Tage bis zum Ende unserer Spendenkampagne.', - 'double-progress-close': 'Das wird knapp.', - 'missing-amount': 'Uns fehlen noch', 'custom-amount-placeholder': 'Wahlbetrag' }; diff --git a/banners/desktop/C24_WMDE_Desktop_DE_11/messages_var.ts b/banners/desktop/C24_WMDE_Desktop_DE_11/messages_var.ts new file mode 100644 index 000000000..1158d8879 --- /dev/null +++ b/banners/desktop/C24_WMDE_Desktop_DE_11/messages_var.ts @@ -0,0 +1,35 @@ +import CustomAmountFormDe from '@src/components/DonationForm/Forms/messages/CustomAmountForm.de'; +import DynamicCampaignTextDe from '@src/utils/DynamicContent/messages/DynamicCampaignText.de'; +import { TranslationMessages } from '@src/Translator'; +import UpgradeToYearlyDe from '@src/components/DonationForm/Forms/messages/UpgradeToYearly.de'; +import AddressFormDe from '@src/components/DonationForm/Forms/messages/AddressForm.de'; +import FooterDe from '@src/components/Footer/messages/Footer.de'; +import MainDonationFormDe from '@src/components/DonationForm/Forms/messages/MainDonationForm.de'; +import FallbackBanner from '@src/components/FallbackBanner/messages/FallbackBanner.de'; +import AlreadyDonatedModal from '@src/components/AlreadyDonatedModal/translations/AlreadyDonatedModal.de'; +import SoftCloseDe from '@src/components/SoftClose/messages/SoftClose.de'; +import DoubleProgressBar from '@src/components/ProgressBar/messages/DoubleProgressBar.de'; + +const messages: TranslationMessages = { + ...CustomAmountFormDe, + ...DynamicCampaignTextDe, + ...UpgradeToYearlyDe, + ...AddressFormDe, + ...FooterDe, + ...MainDonationFormDe, + ...AlreadyDonatedModal, + ...FallbackBanner, + ...SoftCloseDe, + ...DoubleProgressBar, + 'already-donated-go-away-button': 'Im Moment nicht', + 'soft-close-prompt': 'Dürfen wir später nochmal fragen?', + 'upgrade-to-yearly-copy': `

Jedes Jahr sind wir auf Menschen wie Sie angewiesen. Jährliche Spenden helfen uns besonders und ermöglichen langfristige Weiterentwicklungen.

+

Sie gehen kein Risiko ein: Jederzeit formlos zu sofort kündbar.

`, + 'upgrade-to-yearly-no': 'Nein, ich spende einmalig {{amount}}', + 'upgrade-to-yearly-yes': 'Ja, ich spende {{amount}} jährlich', + 'campaign-day-only-n-days': 'Heute sind es nur noch {{days}} Tage bis zum Ende unserer Spendenkampagne.', + 'custom-amount-placeholder': 'Wahlbetrag', + 'prefix-days-left': 'Noch' +}; + +export default messages; diff --git a/banners/desktop/C24_WMDE_Desktop_DE_11/styles/MainBannerVar.scss b/banners/desktop/C24_WMDE_Desktop_DE_11/styles/MainBannerVar.scss new file mode 100644 index 000000000..84ffed429 --- /dev/null +++ b/banners/desktop/C24_WMDE_Desktop_DE_11/styles/MainBannerVar.scss @@ -0,0 +1,53 @@ +$banner-height: 357px !default; +$form-width: 300px !default; + +.wmde-banner { + + .previous { + --slider-chevron: var( --previous-button-fill ); + } + + &-main { + min-height: $banner-height; + display: flex; + flex-direction: column; + padding: 12px 24px 0; + } + + &-content { + display: flex; + flex-direction: row; + flex-grow: 1; + } + + &-message { + padding: 3px 15px; + color: var( --message-color ); + background-color: var( --message-background ); + border: none; + border-radius: 9px; + margin-bottom: 5px; + } + + &-column-left { + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + flex: 1 1 auto; + margin-bottom: 0; + overflow-y: hidden; + margin-right: 30px; + padding: 0; + border: none; + } + + &-column-right { + order: 2; + flex: 0 0 $form-width; + display: flex; + flex-direction: column; + width: $form-width; + padding: 10px 0 0; + } +} diff --git a/banners/desktop/C24_WMDE_Desktop_DE_11/styles/styles_var.scss b/banners/desktop/C24_WMDE_Desktop_DE_11/styles/styles_var.scss new file mode 100644 index 000000000..6df7a6018 --- /dev/null +++ b/banners/desktop/C24_WMDE_Desktop_DE_11/styles/styles_var.scss @@ -0,0 +1,56 @@ +// This is the file where we import the theme-specific component styles +@use '../../../../src/themes/Svingle/swatches/skin_default' with ( + $slider: true, + $select-group: true, + $upgrade-to-yearly: true, + $fallback-banner: true, + $double-progress-bar: true, + $soft-close: true, +); +@use 'src/components/BannerConductor/banner-transition'; +@use 'src/themes/UseOfFunds/swatches/skin_default' as uof-default; +@use 'Banner'; +@use 'src/themes/UseOfFunds/UseOfFunds'; +@use 'MainBannerVar' with ( + $banner-height: 357px, + $form-width: 300px +); +@use 'src/themes/Svingle/defaults'; +@use 'src/themes/Svingle/ButtonClose/ButtonClose'; +@use 'src/themes/Svingle/ProgressBar/DoubleProgressBar'; +@use 'src/themes/Svingle/DonationForm/DonationForm'; +@use 'src/themes/Svingle/DonationForm/MultiStepDonation'; +@use 'src/themes/Svingle/DonationForm/SubComponents/SelectGroup'; +@use 'src/themes/Svingle/DonationForm/SubComponents/SelectGroupRadios' with ( + $font-size: 14px +); +@use 'src/themes/Svingle/DonationForm/SubComponents/SelectCustomAmountRadio' with ( + $font-size: 14px +); +@use 'src/themes/Svingle/DonationForm/SubComponents/SmsBox'; +@use 'src/themes/Svingle/DonationForm/Forms/MainDonationForm'; +@use 'src/themes/Svingle/DonationForm/Forms/UpgradeToYearlyButtonForm' with ( + $font-size: 14px +); +@use 'src/themes/Svingle/DonationForm/Forms/CustomAmountForm'; +@use 'src/themes/Svingle/Footer/FooterAlreadyDonated'; +@use 'src/themes/Svingle/Footer/SelectionInput'; +@use 'src/themes/Svingle/Message/Message'; +@use 'src/themes/Svingle/SoftClose/SoftClose'; +@use 'src/themes/Svingle/Slider/KeenSlider' with ( + $margin-bottom: 5px +); + +/** + * Fallback banner with "Fijitiv" theme + * All selectors in Fijitiv theme are prefixed with the ".wmde-banner-fallback" class selector, + so they override the "default" styles with the same selector + */ +@use 'FallbackBanner'; +@use 'src/themes/Fijitiv/FallbackBanner/FallbackButton'; +@use 'src/themes/Fijitiv/FallbackBanner/LargeFooter'; +@use 'src/themes/Fijitiv/FallbackBanner/SmallFooter'; +@use 'src/themes/Fijitiv/ProgressBar/ProgressBar' as FallbackProgressBar with ( + $progress-bar-margin: 0 15px +); +@use 'src/themes/Fijitiv/Slider/KeenSlider' as FallbackSlider; diff --git a/src/components/ProgressBar/DoubleProgressBar.vue b/src/components/ProgressBar/DoubleProgressBar.vue index 9ad5fa79d..41caf0912 100644 --- a/src/components/ProgressBar/DoubleProgressBar.vue +++ b/src/components/ProgressBar/DoubleProgressBar.vue @@ -1,20 +1,27 @@ @@ -24,7 +31,7 @@ import { inject } from 'vue'; import { DynamicContent } from '@src/utils/DynamicContent/DynamicContent'; -const { progressBarContent, daysLeftSentence } = inject( 'dynamicCampaignText' ); +const { progressBarContent, daysLeftSentence, daysPassedSentence } = inject( 'dynamicCampaignText' ); const currentCampaignTimePercentage = inject( 'currentCampaignTimePercentage' ); diff --git a/src/components/ProgressBar/messages/DoubleProgressBar.de.ts b/src/components/ProgressBar/messages/DoubleProgressBar.de.ts new file mode 100644 index 000000000..22dd66c07 --- /dev/null +++ b/src/components/ProgressBar/messages/DoubleProgressBar.de.ts @@ -0,0 +1,7 @@ +import { TranslationMessages } from '@src/Translator'; + +const translations: TranslationMessages = { + 'double-progress-close': 'Das wird knapp.' +}; + +export default translations; diff --git a/src/themes/Svingle/ProgressBar/DoubleProgressBar.scss b/src/themes/Svingle/ProgressBar/DoubleProgressBar.scss index aeee7728a..c3eacf459 100644 --- a/src/themes/Svingle/ProgressBar/DoubleProgressBar.scss +++ b/src/themes/Svingle/ProgressBar/DoubleProgressBar.scss @@ -1,5 +1,4 @@ @use 'src/components/ProgressBar/DoubleProgressBar'; -@use '../variables/globals'; @use 'transitionsDouble'; .wmde-banner { @@ -10,7 +9,6 @@ margin: 0; font-weight: bold; font-size: 12px; - color: var( --color-white ); &-amount, &-time { @@ -21,19 +19,25 @@ &-fill, &-difference { + color: var( --double-progress-color ); height: 100%; padding: 0 10px; } &-fill { display: flex; - justify-content: space-between; + justify-content: right; } } + &-right-text { + float: right; + color: var( --double-progress-right-text-color ); + } + &-amount { margin-bottom: 5px; - background: var( --color-primary-light ); + background: var( --double-progress-time-background ); &-fill, &-difference { @@ -41,24 +45,30 @@ } &-fill { - background: var( --color-primary ); + background: var( --double-progress-time-fill-background ); z-index: 2; } &-difference { - color: var( --color-white ); - background: var( --color-primary-bright ); + background: var( --double-progress-difference-background ); z-index: 1; text-align: right; } } &-time { - background: var( --color-secondary-lighter ); + background: var( --double-progress-amount-background ); &-fill { border-radius: 12px; - background: var( --color-secondary ); + background: var( --double-progress-amount-fill-background ); + } + } + + &.is-late-progress { + .wmde-banner-double-progress-amount-fill, + .wmde-banner-double-progress-time-fill { + justify-content: space-between; } } } diff --git a/src/themes/Svingle/ProgressBar/_transitionsDouble.scss b/src/themes/Svingle/ProgressBar/_transitionsDouble.scss index 5f8911cbd..e7133f121 100644 --- a/src/themes/Svingle/ProgressBar/_transitionsDouble.scss +++ b/src/themes/Svingle/ProgressBar/_transitionsDouble.scss @@ -7,12 +7,6 @@ transition: width 3000ms globals.$banner-easing; width: 0; max-width: 100%; - - > div { - opacity: 0; - transition: opacity 300ms globals.$banner-easing; - transition-delay: 3000ms; - } } &-double-progress-amount-difference, @@ -24,6 +18,12 @@ min-width: 100px; } + &-double-progress .text-fade { + opacity: 0; + transition: opacity 300ms globals.$banner-easing; + transition-delay: 3000ms; + } + &--visible { .wmde-banner-double-progress-amount-difference, .wmde-banner-double-progress-time-fill { @@ -34,11 +34,8 @@ width: var( --wmde-banner-progress-bar-width ); } - .wmde-banner-double-progress-amount-fill, - .wmde-banner-double-progress-time-fill { - > div { - opacity: 1; - } + .wmde-banner-double-progress .text-fade { + opacity: 1; } } } diff --git a/src/themes/Svingle/swatches/color_dark.scss b/src/themes/Svingle/swatches/color_dark.scss index 9339f2a8d..39b16d023 100644 --- a/src/themes/Svingle/swatches/color_dark.scss +++ b/src/themes/Svingle/swatches/color_dark.scss @@ -12,9 +12,11 @@ $grey700: #292929; $grey800: #1f1f1f; $red100: #ff8888; +$red500: #fb4848; $red600: #990a00; $red700: #7d0a00; +$blue100: #dff1ff; $blue600: #4465a7; $blue700: #2a4b8d; $blue800: #20303f; @@ -22,6 +24,7 @@ $blue800: #20303f; @mixin swatch( $slider: false, $progress-bar: false, + $double-progress-bar: false, $select-group: false, $select-group-bubble: false, $select-group-button: false, @@ -80,6 +83,17 @@ $blue800: #20303f; --progress-bar-fill-color: #{$white}; } + @if $double-progress-bar { + --double-progress-color: #{$white}; + --double-progress-time-background: #{$red100}; + --double-progress-time-fill-background: #{$red600}; + --double-progress-difference-background: #{$red500}; + + --double-progress-amount-background: #{$blue100}; + --double-progress-amount-fill-background: #{$blue700}; + --double-progress-right-text-color: #{$grey700}; + } + @if $select-group { --select-group-background: #{$white}; --select-group-radio-label-color: #{$black}; diff --git a/src/themes/Svingle/swatches/color_light.scss b/src/themes/Svingle/swatches/color_light.scss index 53f46a9f5..96abebf82 100644 --- a/src/themes/Svingle/swatches/color_light.scss +++ b/src/themes/Svingle/swatches/color_light.scss @@ -10,9 +10,12 @@ $grey550: #72777d; $grey600: #747474; $grey700: #202122; +$red100: #ffe8e8; +$red500: #fb4848; $red600: #990a00; $red700: #7d0a00; +$blue100: #dff1ff; $blue600: #4465a7; $blue650: #3366cc; $blue700: #2a4b8d; @@ -20,6 +23,7 @@ $blue700: #2a4b8d; @mixin swatch( $slider: false, $progress-bar: false, + $double-progress-bar: false, $select-group: false, $select-group-bubble: false, $select-group-button: false, @@ -80,6 +84,17 @@ $blue700: #2a4b8d; --progress-bar-fill-color: #{$white}; } + @if $double-progress-bar { + --double-progress-color: #{$white}; + --double-progress-time-background: #{$red100}; + --double-progress-time-fill-background: #{$red600}; + --double-progress-difference-background: #{$red500}; + + --double-progress-amount-background: #{$blue100}; + --double-progress-amount-fill-background: #{$blue700}; + --double-progress-right-text-color: #{$grey700}; + } + @if $select-group { --select-group-background: #{$white}; --select-group-radio-label-color: #{$black}; diff --git a/src/themes/Svingle/swatches/skin_default.scss b/src/themes/Svingle/swatches/skin_default.scss index 46cecd869..b01cd9534 100644 --- a/src/themes/Svingle/swatches/skin_default.scss +++ b/src/themes/Svingle/swatches/skin_default.scss @@ -2,6 +2,7 @@ $slider: false !default; $progress-bar: false !default; +$double-progress-bar: false !default; $select-group: false !default; $select-group-bubble: false !default; $select-group-button: false !default; @@ -15,6 +16,7 @@ $minimised-banner: false !default; @include color_light.swatch( $slider, $progress-bar, + $double-progress-bar, $select-group, $select-group-bubble, $select-group-button, diff --git a/src/themes/Svingle/swatches/skin_vector-2022.scss b/src/themes/Svingle/swatches/skin_vector-2022.scss index aed768b16..137465f8d 100644 --- a/src/themes/Svingle/swatches/skin_vector-2022.scss +++ b/src/themes/Svingle/swatches/skin_vector-2022.scss @@ -3,6 +3,7 @@ $slider: false !default; $progress-bar: false !default; +$double-progress-bar: false !default; $select-group: false !default; $select-group-bubble: false !default; $select-group-button: false !default; @@ -16,6 +17,7 @@ $minimised-banner: false !default; @include color_light.swatch( $slider, $progress-bar, + $double-progress-bar, $select-group, $select-group-bubble, $select-group-button, @@ -31,6 +33,7 @@ $minimised-banner: false !default; @include color_dark.swatch( $slider, $progress-bar, + $double-progress-bar, $select-group, $select-group-bubble, $select-group-button, @@ -47,6 +50,7 @@ $minimised-banner: false !default; @include color_dark.swatch( $slider, $progress-bar, + $double-progress-bar, $select-group, $select-group-bubble, $select-group-button, @@ -62,6 +66,7 @@ $minimised-banner: false !default; @include color_light.swatch( $slider, $progress-bar, + $double-progress-bar, $select-group, $select-group-bubble, $select-group-button, diff --git a/src/utils/DynamicContent/DynamicCampaignText.ts b/src/utils/DynamicContent/DynamicCampaignText.ts index 9b4b6d887..14d7c4bcc 100644 --- a/src/utils/DynamicContent/DynamicCampaignText.ts +++ b/src/utils/DynamicContent/DynamicCampaignText.ts @@ -15,6 +15,7 @@ import { ProgressBarContent } from '@src/utils/DynamicContent/generators/Progres import { DynamicProgressBarContent } from '@src/utils/DynamicContent/DynamicProgressBarContent'; import { CurrentTime } from '@src/utils/DynamicContent/generators/CurrentTime'; import { DateAndTime } from '@src/utils/DynamicContent/DateAndTime'; +import { DaysPassedSentence } from '@src/utils/DynamicContent/generators/DaysPassedSentence'; export default class DynamicCampaignText implements DynamicContent { private readonly _date: Date; @@ -116,6 +117,12 @@ export default class DynamicCampaignText implements DynamicContent { } ); } + public get daysPassedSentence(): string { + return this.getCachedValue( 'daysPassedSentence', () => { + return new DaysPassedSentence( this.getCampaignTimeRange(), this._translator ).getText(); + } ); + } + private getCampaignProjection(): CampaignProjection { if ( this._campaignProjection === undefined ) { const projectionRange = TimeRange.createFromStrings( diff --git a/src/utils/DynamicContent/DynamicContent.ts b/src/utils/DynamicContent/DynamicContent.ts index 74b42a335..b19ae60c0 100644 --- a/src/utils/DynamicContent/DynamicContent.ts +++ b/src/utils/DynamicContent/DynamicContent.ts @@ -10,6 +10,7 @@ export interface DynamicContent { currentDate: string; getCurrentDateAndTime: () => DateAndTime; daysLeftSentence: string; + daysPassedSentence: string; campaignDaySentence: string; visitorsVsDonorsSentence: string; donorsNeededSentence: string; diff --git a/src/utils/DynamicContent/generators/DaysPassedSentence.ts b/src/utils/DynamicContent/generators/DaysPassedSentence.ts new file mode 100644 index 000000000..58c6113e4 --- /dev/null +++ b/src/utils/DynamicContent/generators/DaysPassedSentence.ts @@ -0,0 +1,27 @@ +import { TextGenerator } from '@src/utils/DynamicContent/generators/TextGenerator'; +import TimeRange from '@src/utils/TimeRange'; +import { Translator } from '@src/Translator'; + +export class DaysPassedSentence implements TextGenerator { + private readonly _campaignDays: TimeRange; + private readonly _translator: Translator; + + public constructor( campaignDays: TimeRange, translator: Translator ) { + this._campaignDays = campaignDays; + this._translator = translator; + } + + public getText(): string { + const daysSinceStart = this._campaignDays.daysSinceStart(); + return [ + daysSinceStart, + this.getDayTranslation( daysSinceStart ) + ].join( ' ' ); + } + + private getDayTranslation( daysSinceStart: number ): string { + return daysSinceStart === 1 ? + this._translator.translate( 'day-singular' ) : + this._translator.translate( 'day-plural' ); + } +} diff --git a/test/banners/desktop/C24_WMDE_Desktop_DE_11/components/BannerVar.spec.ts b/test/banners/desktop/C24_WMDE_Desktop_DE_11/components/BannerVar.spec.ts new file mode 100644 index 000000000..68b5ec3d4 --- /dev/null +++ b/test/banners/desktop/C24_WMDE_Desktop_DE_11/components/BannerVar.spec.ts @@ -0,0 +1,182 @@ +import { afterEach, beforeEach, describe, test, vi } from 'vitest'; +import { mount, VueWrapper } from '@vue/test-utils'; +import Banner from '@banners/desktop/C24_WMDE_Desktop_DE_11/components/BannerVar.vue'; +import { BannerStates } from '@src/components/BannerConductor/StateMachine/BannerStates'; +import { newDynamicContent } from '@test/banners/dynamicCampaignContent'; +import { useOfFundsContent } from '@test/banners/useOfFundsContent'; +import { formItems } from '@test/banners/formItems'; +import { CurrencyEn } from '@src/utils/DynamicContent/formatters/CurrencyEn'; +import { useOfFundsFeatures } from '@test/features/UseOfFunds'; +import { + bannerContentAnimatedTextFeatures, + bannerContentDateAndTimeFeatures, + bannerContentDisplaySwitchFeatures, + bannerContentFeatures +} from '@test/features/BannerContent'; +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 { bannerAutoHideFeatures, bannerMainFeatures } from '@test/features/MainBanner'; +import { formActionSwitchFeatures } from '@test/features/form_action_switch/MainDonation_UpgradeToYearlyButton'; +import { softCloseFeatures } from '@test/features/SoftCloseDesktop'; +import { alreadyDonatedModalFeatures } from '@test/features/AlreadyDonatedModal'; +import { softCloseSubmitTrackingFeaturesDesktop } from '@test/features/SoftCloseSubmitTrackingDesktop'; +import { Tracker } from '@src/tracking/Tracker'; + +const formModel = useFormModel(); +const translator = ( key: string ): string => key; +let tracker: Tracker; + +describe( 'BannerVar.vue', () => { + + beforeEach( () => { + resetFormModel( formModel ); + vi.useFakeTimers(); + tracker = { + trackEvent: vi.fn() + }; + } ); + + afterEach( () => { + vi.restoreAllMocks(); + vi.useRealTimers(); + } ); + + const getWrapper = ( dynamicContent: DynamicContent = null ): VueWrapper => { + return mount( Banner, { + attachTo: document.body, + props: { + bannerState: BannerStates.Pending, + useOfFundsContent, + remainingImpressions: 10, + localCloseTracker: { + getItem: () => '', + setItem: () => {} + } + }, + global: { + mocks: { + $translate: translator + }, + provide: { + translator: { translate: translator }, + dynamicCampaignText: dynamicContent ?? newDynamicContent(), + currentCampaignTimePercentage: 42, + formActions: { + donateWithAddressAction: 'https://example.com/with-address', + donateAnonymouslyAction: 'https://example.com/without-address' + }, + currencyFormatter: new CurrencyEn(), + formItems, + tracker + } + } + } ); + }; + + describe( 'Main Banner', () => { + test.each( [ + [ 'expectDoesNotEmitCloseEvent' ] + ] )( '%s', async ( testName: string ) => { + await bannerMainFeatures[ testName ]( getWrapper() ); + } ); + + test.each( [ + [ 'expectClosesBannerWhenWindowBecomesSmall' ] + ] )( '%s', async ( testName: string ) => { + await bannerAutoHideFeatures[ testName ]( getWrapper ); + } ); + } ); + + describe( 'Content', () => { + test.each( [ + [ 'expectSlideShowPlaysWhenBecomesVisible' ], + [ 'expectSlideShowStopsOnFormInteraction' ] + ] )( '%s', async ( testName: string ) => { + await bannerContentFeatures[ testName ]( getWrapper() ); + } ); + + test.each( [ + [ 'expectShowsSlideShowOnSmallSizes' ], + [ 'expectShowsMessageOnLargeSizes' ] + ] )( '%s', async ( testName: string ) => { + await bannerContentDisplaySwitchFeatures[ testName ]( getWrapper, 1300 ); + } ); + + test.each( [ + [ 'expectShowsAnimatedVisitorsVsDonorsSentenceInMessage' ], + [ 'expectShowsAnimatedVisitorsVsDonorsSentenceInSlideShow' ] + ] )( '%s', async ( testName: string ) => { + await bannerContentAnimatedTextFeatures[ testName ]( getWrapper ); + } ); + + test.each( [ + [ 'expectShowsLiveDateAndTimeInMessage' ], + [ 'expectShowsLiveDateAndTimeInSlideshow' ] + ] )( '%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' ], + [ 'expectMainDonationFormSubmitsWithAddressForPayPal' ], + [ 'expectUpgradeToYearlyFormSubmitsWithAddressForDirectDebit' ], + [ 'expectUpgradeToYearlyFormSubmitsWithAddressForPayPal' ] + ] )( '%s', async ( testName: string ) => { + await formActionSwitchFeatures[ testName ]( getWrapper() ); + } ); + } ); + + describe( 'Soft Close', () => { + test.each( [ + [ 'expectShowsSoftClose' ], + [ 'expectEmitsSoftCloseCloseEvent' ], + [ 'expectEmitsSoftCloseMaybeLaterEvent' ], + [ 'expectEmitsSoftCloseTimeOutEvent' ], + [ 'expectEmitsBannerContentChangedOnSoftClose' ], + [ 'expectShowsCloseIcon' ], + [ 'expectCloseIconEmitsCloseEvent' ] + ] )( '%s', async ( testName: string ) => { + await softCloseFeatures[ testName ]( getWrapper() ); + } ); + } ); + + describe( 'Soft Close Submit Tracking', () => { + test.each( [ + [ 'expectEmitsBannerSubmitOnReturnEvent' ], + [ 'expectDoesNotEmitsBannerSubmitOnReturnEventWhenLocalStorageItemIsMissing' ] + ] )( '%s', async ( testName: string ) => { + await softCloseSubmitTrackingFeaturesDesktop[ testName ]( getWrapper(), tracker ); + } ); + } ); + + describe( 'Already Donated', () => { + test.each( [ + [ 'expectFiresMaybeLaterEventOnLinkClick' ] + ] )( '%s', async ( testName: string ) => { + await alreadyDonatedModalFeatures[ testName ]( getWrapper() ); + } ); + } ); + + describe( 'Use of Funds', () => { + test.each( [ + [ 'expectShowsUseOfFunds' ], + [ 'expectHidesUseOfFunds' ] + ] )( '%s', async ( testName: string ) => { + await useOfFundsFeatures[ testName ]( getWrapper() ); + } ); + } ); +} ); diff --git a/test/banners/dynamicCampaignContent.ts b/test/banners/dynamicCampaignContent.ts index 3fdb991be..e06f8de9b 100644 --- a/test/banners/dynamicCampaignContent.ts +++ b/test/banners/dynamicCampaignContent.ts @@ -10,6 +10,7 @@ export function newDynamicContent(): DynamicContent { }, currentDayName: '', daysLeftSentence: 'daysLeftSentence', + daysPassedSentence: 'daysPassedSentence', donorsNeededSentence: '', goalDonationSum: '', remainingDonationSum: '', diff --git a/test/components/ProgressBar/DoubleProgressBar.spec.ts b/test/components/ProgressBar/DoubleProgressBar.spec.ts index e07a40619..4ef82abee 100644 --- a/test/components/ProgressBar/DoubleProgressBar.spec.ts +++ b/test/components/ProgressBar/DoubleProgressBar.spec.ts @@ -16,6 +16,7 @@ describe( 'DoubleProgressBar.vue', () => { }, currentDayName: '', daysLeftSentence: 'daysLeftSentence', + daysPassedSentence: 'daysPassedSentence', donorsNeededSentence: '', goalDonationSum: '', remainingDonationSum: '', @@ -51,4 +52,50 @@ describe( 'DoubleProgressBar.vue', () => { expect( wrapper.attributes( 'style' ) ).toContain( '--wmde-banner-progress-bar-time-width: 66%' ); } ); + it( 'shows the early progress content', () => { + dynamicCampaignContent.progressBarContent.isLateProgress = false; + const wrapper = mount( ProgressBar, { + props: { amountToShowOnRight: 'TARGET' }, + global: { + mocks: { + $translate: ( key: string ) => key + }, + provide: { + dynamicCampaignText: dynamicCampaignContent, + currentCampaignTimePercentage: 66 + } + } + } ); + + expect( wrapper.classes() ).not.toContain( 'is-late-progress' ); + expect( wrapper.find( '.wmde-banner-double-progress-amount .wmde-banner-double-progress-right-text' ).exists() ).toBeTruthy(); + expect( wrapper.find( '.wmde-banner-double-progress-time .wmde-banner-double-progress-right-text' ).exists() ).toBeTruthy(); + expect( wrapper.find( '.amount-needed' ).exists() ).toBeFalsy(); + expect( wrapper.find( '.close-text' ).exists() ).toBeFalsy(); + expect( wrapper.find( '.days-left' ).exists() ).toBeFalsy(); + } ); + + it( 'shows the late progress content', () => { + dynamicCampaignContent.progressBarContent.isLateProgress = true; + const wrapper = mount( ProgressBar, { + props: { amountToShowOnRight: 'TARGET' }, + global: { + mocks: { + $translate: ( key: string ) => key + }, + provide: { + dynamicCampaignText: dynamicCampaignContent, + currentCampaignTimePercentage: 66 + } + } + } ); + + expect( wrapper.classes() ).toContain( 'is-late-progress' ); + expect( wrapper.find( '.wmde-banner-double-progress-amount .wmde-banner-double-progress-right-text' ).exists() ).toBeFalsy(); + expect( wrapper.find( '.wmde-banner-double-progress-time .wmde-banner-double-progress-right-text' ).exists() ).toBeFalsy(); + expect( wrapper.find( '.amount-needed' ).exists() ).toBeTruthy(); + expect( wrapper.find( '.close-text' ).exists() ).toBeTruthy(); + expect( wrapper.find( '.days-left' ).exists() ).toBeTruthy(); + } ); + } ); diff --git a/test/components/ProgressBar/ProgressBar.spec.ts b/test/components/ProgressBar/ProgressBar.spec.ts index 33b69284e..4d9748bca 100644 --- a/test/components/ProgressBar/ProgressBar.spec.ts +++ b/test/components/ProgressBar/ProgressBar.spec.ts @@ -16,6 +16,7 @@ describe( 'ProgressBar.vue', () => { }, currentDayName: '', daysLeftSentence: 'daysLeftSentence', + daysPassedSentence: 'daysPassedSentence', donorsNeededSentence: '', goalDonationSum: '', remainingDonationSum: '', diff --git a/test/unit/utils/DynamicContent/DynamicCampaignText.spec.ts b/test/unit/utils/DynamicContent/DynamicCampaignText.spec.ts index 4c06b1799..21c273f83 100644 --- a/test/unit/utils/DynamicContent/DynamicCampaignText.spec.ts +++ b/test/unit/utils/DynamicContent/DynamicCampaignText.spec.ts @@ -100,6 +100,10 @@ describe( 'DynamicCampaignText', () => { expect( dynamicCampaignText.daysLeftSentence ).toBe( 'only 52 days left' ); } ); + it( 'Gets the days passed sentence', () => { + expect( dynamicCampaignText.daysPassedSentence ).toBe( '8 days' ); + } ); + it( 'Gets the donors needed sentence', () => { expect( dynamicCampaignText.donorsNeededSentence ).toBe( '445000' ); } ); diff --git a/test/unit/utils/DynamicContent/generators/DaysPassedSentence.spec.ts b/test/unit/utils/DynamicContent/generators/DaysPassedSentence.spec.ts new file mode 100644 index 000000000..647f50076 --- /dev/null +++ b/test/unit/utils/DynamicContent/generators/DaysPassedSentence.spec.ts @@ -0,0 +1,25 @@ +import { describe, it, expect } from 'vitest'; +import TimeRange from '@src/utils/TimeRange'; +import { Translator } from '@src/Translator'; +import { DaysPassedSentence } from '@src/utils/DynamicContent/generators/DaysPassedSentence'; + +describe( 'DaysPassedSentence', function () { + const translator = new Translator( { + 'prefix-days-left': 'only', + 'suffix-days-left': 'left', + 'day-singular': 'day', + 'day-plural': 'days' + } ); + + it( 'should return a sentence for the first day', function () { + const campaignDays = new TimeRange( new Date( 2023, 10, 11 ), new Date( 2024, 0, 1 ), new Date( 2023, 10, 12 ) ); + const daysLeft = new DaysPassedSentence( campaignDays, translator ); + expect( daysLeft.getText() ).toBe( '1 day' ); + } ); + + it( 'should return a sentence for other days', function () { + const campaignDays = new TimeRange( new Date( 2023, 10, 11 ), new Date( 2024, 0, 1 ), new Date( 2023, 11, 25 ) ); + const daysLeft = new DaysPassedSentence( campaignDays, translator ); + expect( daysLeft.getText() ).toBe( '44 days' ); + } ); +} );