Page cover image

βž—DNT-2843 EVCalculator

Controls:

Name
Description
Default
Control

inputs*

InputsType

-

-

output*

YearlyOutputProps

-

-

analytics

ComponentEvent

-

-

backgroundColor

string

-

-

bgColorClass

"""bg-gray-lighter"

-

-

ctaText

any

-

-

fullWidth

boolean

-

-

image

any

-

-

isCtaAButton

boolean

-

-

link

any

-

-

mobileLink

any

-

-

modal

{ id: string; url?: string; } | null

-

-

subtitle

any

-

-

title

any

-

-

//Calculator/index.tsx

import React from "react";
import Input from "./inputComponents/Input";
import Stepper from "./inputComponents/Stepper";
import Slider from "./inputComponents/Slider";

const Calculator = () => (
  <section className="px-16 md:px-24 py-32 md:py-48">
    <div className="mb-48 items-center flex">
      <Slider
        prompt="Prompt goes here"
        defaultVal="88888"
        min="0"
        max="100000"
        label="Number Slider Title"
      />
    </div>
    <div className="flex flex-1">
      <Stepper prompt="Prompt goes here" defaultVal="1" />
      <Input
        prompt="Prompt goes here"
        defaultVal="500"
        label="per month"
        isInDollars={true}
      />
    </div>
  </section>
);

export default Calculator;
//src/components/Calculator/outputComponents/YearlySavings.tsx

import { YearlyOutputProps } from "../types";

const avgDaysInMonth = 30.437;
const avgDaysInYear = 365.25;

const formatMoney = (money: number) => {
  const roundedNum = Math.round(money * 100) / 100;
  const arr = roundedNum.toLocaleString().split(".");
  const cents = `${arr[1] || ""}00`.slice(0, 2);
  const dollars = arr[0];
  return {
    dollars,
    cents,
    full: `$${dollars}.${cents}`,
  };
};

const YearlySavings = ({
  dailySavings = 0,
  headline = "Total Yearly Savings",
  monthlyText = "Monthly Savings",
  dailyText = "Daily Savings",
  description,
}: YearlyOutputProps) => {
  const annual = formatMoney(dailySavings * avgDaysInYear);
  const monthly = formatMoney(dailySavings * avgDaysInMonth);
  const daily = formatMoney(dailySavings);

  return (
    <div>
      <h4 className="text-blue text-xl-fixed">{headline}</h4>
      {/* need to adjust line height to vertically top align */}
      <p
        className="text-blue my-12 text-2xl-fixed align-top"
        style={{ lineHeight: "55px" }}
      >
        $
        <span className="text-3xl-fixed align-top leading-none">
          {annual.dollars}
        </span>
        {`.${annual.cents}`}
      </p>
      <div className="flex flex-row justify-center">
        <div className="border-r px-32 border-gray">
          <p
            className="text-blue align-top text-lg-fixed"
            style={{ lineHeight: "25px" }}
          >
            $<span className="text-xl-fixed align-top">{daily.dollars}</span>
            {`.${daily.cents}`}
          </p>
          <p className="text-gray-dark text-xs">{dailyText}</p>
        </div>
        <div className="px-32">
          <p
            className="text-blue align-top text-lg-fixed"
            style={{ lineHeight: "25px" }}
          >
            $<span className="text-xl-fixed align-top">{monthly.dollars}</span>
            {`.${monthly.cents}`}
          </p>
          <p className="text-gray-dark text-xs">{monthlyText}</p>
        </div>
      </div>
      {description && (
        <p className="mt-32 text-gray-dark text-md">{description}</p>
      )}
    </div>
  );
};

export default YearlySavings;
//src/components/Calculator/test.tsx

import "@testing-library/jest-dom";
import Input from "src/components/Calculator/inputComponents/Input";
import Slider from "src/components/Calculator/inputComponents/Slider";
import YearlySavings from "./outputComponents/YearlySavings";
import { renderWithCTX, screen, fireEvent } from "src/lib/testWrappers";

const props: Parameters<typeof Input>[0] = {
  prompt: "Current Gas Price",
  defaultVal: "3.12",
  label: "per gallon",
  isInDollars: true,
};

describe("calculator input", () => {
  it("should render", () => {
    renderWithCTX(<Input {...props} />);
    const input = screen.getByRole("textbox", {
      name: `${props?.prompt} ${props?.label}`,
    });
    expect(input).toBeInTheDocument();
    expect(input).toHaveAttribute("value", props.defaultVal);
  });
  it("should prefix with $ if applicable", () => {
    const { rerender } = renderWithCTX(<Input {...props} />);
    let currency: HTMLElement | null = screen.getByText(/\$/i);
    expect(currency).toBeInTheDocument();
    rerender(<Input {...props} isInDollars={false} />);
    currency = screen.queryByText(/\$/i);
    expect(currency).not.toBeInTheDocument();
  });
});

describe("calculator slider input", () => {
  const props = {
    prompt: "How many miles do you drive daily?",
    defaultVal: "160",
    label: "Daily Miles",
    min: "0",
    max: "320",
    step: "1",
  };
  it("should render", () => {
    renderWithCTX(<Slider {...props} />);
    const slider = screen.getByRole("slider", {
      name: `${props.prompt} ${props.label}`,
    });
    const input = screen.getByRole("textbox", {
      name: `${props.prompt} ${props.label}`,
    });
    const label = screen.getByText(/daily miles/i);
    const heading = screen.getByRole("heading", {
      name: /how many miles do you drive daily\?/i,
    });
    expect(slider).toBeInTheDocument();
    expect(input).toBeInTheDocument();
    expect(label).toBeInTheDocument();
    expect(heading).toBeInTheDocument();
  });
  it("should default to midpoint between min and max when defaultVal is missing", () => {
    renderWithCTX(<Slider {...props} defaultVal="" min="100" max="300" />);
    const slider = screen.getByRole("slider", {
      name: `${props.prompt} ${props.label}`,
    });
    expect(slider).toHaveAttribute("value", "200");
  });
  it("should keep the same slider and text input values", () => {
    renderWithCTX(<Slider {...props} />);
    const slider = screen.getByRole("slider", {
      name: `${props.prompt} ${props.label}`,
    });
    const input = screen.getByRole("textbox", {
      name: `${props.prompt} ${props.label}`,
    });
    // slider changes input
    fireEvent.change(slider, { target: { value: 28 } });
    expect(input).toHaveAttribute("value", "28");
    // input changes slider
    fireEvent.change(input, { target: { value: 140 } });
    expect(slider).toHaveAttribute("value", "140");
  });
});

describe("yearly savings output", () => {
  const props = {
    headline: "Yearly Savings",
    monthlyText: "Monthly Savings",
    dailyText: "Daily Savings",
    dailySavings: 2.22,
  };
  it("should render", () => {
    renderWithCTX(<YearlySavings {...props} />);
    const heading = screen.getByRole("heading", { name: props.headline });
    const daily = screen.getByText(props.dailyText);
    const monthly = screen.getByText(props.monthlyText);
    expect(heading).toBeInTheDocument();
    expect(daily).toBeInTheDocument();
    expect(monthly).toBeInTheDocument();
  });
  it("should properly display rounded monetary amounts", () => {
    renderWithCTX(<YearlySavings dailySavings={7.998} />);
    const dollars = screen.getByText("8");
    const cents = screen.getByText(/\$\.00/i);
    expect(dollars).toBeInTheDocument();
    expect(cents).toBeInTheDocument();
  });
});
// Stepper Test.tsx



import '@testing-library/jest-dom';
import Input from 'src/components/Calculator/inputComponents/Input';
import Slider from 'src/components/Calculator/inputComponents/Slider';
import YearlySavings from './outputComponents/YearlySavings';
import { renderWithCTX, screen, fireEvent } from 'src/lib/testWrappers';

const props: Parameters<typeof Input>[0] = {
  prompt: 'Current Gas Price',
  defaultVal: '3.12',
  label: 'per gallon',
  isInDollars: true,
};

describe('calculator input', () => {
  it('should render', () => {
    renderWithCTX(<Input {...props} />);
    const input = screen.getByRole('textbox', {
      name: `${props?.prompt} ${props?.label}`,
    });
    expect(input).toBeInTheDocument();
    expect(input).toHaveAttribute('value', props.defaultVal);
  });
  it('should prefix with $ if applicable', () => {
    const { rerender } = renderWithCTX(<Input {...props} />);
    let currency: HTMLElement | null = screen.getByText(/\$/i);
    expect(currency).toBeInTheDocument();
    rerender(<Input {...props} isInDollars={false} />);
    currency = screen.queryByText(/\$/i);
    expect(currency).not.toBeInTheDocument();
  });
});

describe('calculator slider input', () => {
  const props = {
    prompt: 'How many miles do you drive daily?',
    defaultVal: '160',
    label: 'Daily Miles',
    min: '0',
    max: '320',
    step: '1',
  };
  it('should render', () => {
    renderWithCTX(<Slider {...props} />);
    const slider = screen.getByRole('slider', { name: `${props.prompt} ${props.label}` });
    const input = screen.getByRole('textbox', { name: `${props.prompt} ${props.label}` });
    const label = screen.getByText(/daily miles/i);
    const heading = screen.getByRole('heading', { name: /how many miles do you drive daily\?/i });
    expect(slider).toBeInTheDocument();
    expect(input).toBeInTheDocument();
    expect(label).toBeInTheDocument();
    expect(heading).toBeInTheDocument();
  });
  it('should default to midpoint between min and max when defaultVal is missing', () => {
    renderWithCTX(<Slider {...props} defaultVal="" min="100" max="300" />);
    const slider = screen.getByRole('slider', { name: `${props.prompt} ${props.label}` });
    expect(slider).toHaveAttribute('value', '200');
  });
  it('should keep the same slider and text input values', () => {
    renderWithCTX(<Slider {...props} />);
    const slider = screen.getByRole('slider', { name: `${props.prompt} ${props.label}` });
    const input = screen.getByRole('textbox', { name: `${props.prompt} ${props.label}` });
    // slider changes input
    fireEvent.change(slider, { target: { value: 28 } });
    expect(input).toHaveAttribute('value', '28');
    // input changes slider
    fireEvent.change(input, { target: { value: 140 } });
    expect(slider).toHaveAttribute('value', '140');
  });
});

describe('yearly savings output', () => {
  const props = {
    headline: 'Yearly Savings',
    monthlyText: 'Monthly Savings',
    dailyText: 'Daily Savings',
    dailySavings: 2.22,
  };
  it('should render', () => {
    renderWithCTX(<YearlySavings {...props} />);
    const heading = screen.getByRole('heading', { name: props.headline });
    const daily = screen.getByText(props.dailyText);
    const monthly = screen.getByText(props.monthlyText);
    expect(heading).toBeInTheDocument();
    expect(daily).toBeInTheDocument();
    expect(monthly).toBeInTheDocument();
  });
  it('should properly display rounded monetary amounts', () => {
    renderWithCTX(<YearlySavings dailySavings={7.998} />);
    const dollars = screen.getByText('8');
    const cents = screen.getByText(/\$\.00/i);
    expect(dollars).toBeInTheDocument();
    expect(cents).toBeInTheDocument();
  });
});



describe('calculator slider input', () => {
  const props = {
    prompt: 'How many miles do you drive daily?',
    defaultVal: '160',
    label: 'Daily Miles',
    min: '0',
    max: '320',
    step: '1',
  };
  it('should render', () => {
    renderWithCTX(<Slider {...props} />);
    const slider = screen.getByRole('slider', { name: `${props.prompt} ${props.label}` });
    const input = screen.getByRole('textbox', { name: `${props.prompt} ${props.label}` });
    const label = screen.getByText(/daily miles/i);
    const heading = screen.getByRole('heading', { name: /how many miles do you drive daily\?/i });
    expect(slider).toBeInTheDocument();
    expect(input).toBeInTheDocument();
    expect(label).toBeInTheDocument();
    expect(heading).toBeInTheDocument();
  });
  it('should default to midpoint between min and max when defaultVal is missing', () => {
    renderWithCTX(<Slider {...props} defaultVal="" min="100" max="300" />);
    const slider = screen.getByRole('slider', { name: `${props.prompt} ${props.label}` });
    expect(slider).toHaveAttribute('value', '200');
  });
  it('should keep the same slider and text input values', () => {
    renderWithCTX(<Slider {...props} />);
    const slider = screen.getByRole('slider', { name: `${props.prompt} ${props.label}` });
    const input = screen.getByRole('textbox', { name: `${props.prompt} ${props.label}` });
    // slider changes input
    fireEvent.change(slider, { target: { value: 28 } });
    expect(input).toHaveAttribute('value', '28');
    // input changes slider
    fireEvent.change(input, { target: { value: 140 } });
    expect(slider).toHaveAttribute('value', '140');
  });
});

describe('yearly savings output', () => {
  const props = {
    headline: 'Yearly Savings',
    monthlyText: 'Monthly Savings',
    dailyText: 'Daily Savings',
    dailySavings: 2.22,
  };
  it('should render', () => {
    renderWithCTX(<YearlySavings {...props} />);
    const heading = screen.getByRole('heading', { name: props.headline });
    const daily = screen.getByText(props.dailyText);
    const monthly = screen.getByText(props.monthlyText);
    expect(heading).toBeInTheDocument();
    expect(daily).toBeInTheDocument();
    expect(monthly).toBeInTheDocument();
  });
  it('should properly display rounded monetary amounts', () => {
    renderWithCTX(<YearlySavings dailySavings={7.998} />);
    const dollars = screen.getByText('8');
    const cents = screen.getByText(/\$\.00/i);
    expect(dollars).toBeInTheDocument();
    expect(cents).toBeInTheDocument();
  });
});

Last updated

Was this helpful?