import { Cartesian3 } from "../../Source/Cesium.js";
import { Cartographic } from "../../Source/Cesium.js";
import { ComponentDatatype } from "../../Source/Cesium.js";
import { Ellipsoid } from "../../Source/Cesium.js";
import { EncodedCartesian3 } from "../../Source/Cesium.js";
import { Math as CesiumMath } from "../../Source/Cesium.js";
import { Matrix4 } from "../../Source/Cesium.js";
import { Rectangle } from "../../Source/Cesium.js";
import { Transforms } from "../../Source/Cesium.js";
import { WebMercatorProjection } from "../../Source/Cesium.js";
import { Material } from "../../Source/Cesium.js";
import { MaterialAppearance } from "../../Source/Cesium.js";
import { PerInstanceColorAppearance } from "../../Source/Cesium.js";
import { ShadowVolumeAppearance } from "../../Source/Cesium.js";

describe("Scene/ShadowVolumeAppearance", function () {
  // using ShadowVolumeAppearanceVS directly fails on Travis with the --release test
  var testVs =
    "attribute vec3 position3DHigh;\n" +
    "attribute vec3 position3DLow;\n" +
    "attribute float batchId;\n" +
    "void main() {\n" +
    "    vec4 position = czm_computePosition();\n" +
    "    gl_Position = czm_depthClamp(czm_modelViewProjectionRelativeToEye * position);\n" +
    "}\n";

  var unitSphereEllipsoid = Ellipsoid.UNIT_SPHERE;
  var projection = new WebMercatorProjection(unitSphereEllipsoid);
  var largeTestRectangle = Rectangle.fromDegrees(-45.0, -45.0, 45.0, 45.0);
  var smallTestRectangle = Rectangle.fromDegrees(-0.1, -0.1, 0.1, 0.1);

  var largeRectangleAttributes = ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes(
    largeTestRectangle,
    [0, 0, 0, 1, 1, 0],
    unitSphereEllipsoid,
    projection
  );
  var smallRectangleAttributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(
    smallTestRectangle,
    [0, 0, 0, 1, 1, 0],
    unitSphereEllipsoid,
    projection
  );

  var perInstanceColorMaterialAppearance = new PerInstanceColorAppearance();
  var flatPerInstanceColorMaterialAppearance = new PerInstanceColorAppearance({
    flat: true,
  });

  var textureMaterialAppearance = new MaterialAppearance({
    material: new Material({
      fabric: {
        type: "BumpMap",
        uniforms: {
          image: "../images/Cesium_Logo_Color.jpg",
          channel: "r",
        },
      },
    }),
  });
  var flatTextureMaterialAppearance = new MaterialAppearance({
    material: Material.fromType(Material.ImageType, {
      image: "../Data/images/Red16x16.png",
    }),
    flat: true,
  });

  // Defines for projection extents
  var eastMostCartographic = new Cartographic();
  var longitudeExtentsEncodeScratch = {
    high: 0.0,
    low: 0.0,
  };
  eastMostCartographic.longitude = CesiumMath.PI;
  eastMostCartographic.latitude = 0.0;
  eastMostCartographic.height = 0.0;
  var eastMostCartesian = projection.project(eastMostCartographic);
  var encoded = EncodedCartesian3.encode(
    eastMostCartesian.x,
    longitudeExtentsEncodeScratch
  );
  var eastMostYhighDefine =
    "EAST_MOST_X_HIGH " + encoded.high.toFixed((encoded.high + "").length + 1);
  var eastMostYlowDefine =
    "EAST_MOST_X_LOW " + encoded.low.toFixed((encoded.low + "").length + 1);

  var westMostCartographic = new Cartographic();
  westMostCartographic.longitude = -CesiumMath.PI;
  westMostCartographic.latitude = 0.0;
  westMostCartographic.height = 0.0;
  var westMostCartesian = projection.project(westMostCartographic);
  encoded = EncodedCartesian3.encode(
    westMostCartesian.x,
    longitudeExtentsEncodeScratch
  );
  var westMostYhighDefine =
    "WEST_MOST_X_HIGH " + encoded.high.toFixed((encoded.high + "").length + 1);
  var westMostYlowDefine =
    "WEST_MOST_X_LOW " + encoded.low.toFixed((encoded.low + "").length + 1);

  it("provides attributes for computing texture coordinates from Spherical extents", function () {
    var attributes = largeRectangleAttributes;

    var sphericalExtents = attributes.sphericalExtents;
    expect(sphericalExtents.componentDatatype).toEqual(ComponentDatatype.FLOAT);
    expect(sphericalExtents.componentsPerAttribute).toEqual(4);
    expect(sphericalExtents.normalize).toEqual(false);
    var value = sphericalExtents.value;
    expect(value[0]).toEqualEpsilon(
      -CesiumMath.PI_OVER_FOUR,
      CesiumMath.EPSILON4
    );
    expect(value[1]).toEqualEpsilon(
      -CesiumMath.PI_OVER_FOUR,
      CesiumMath.EPSILON4
    );
    expect(value[2]).toEqualEpsilon(
      1.0 / CesiumMath.PI_OVER_TWO,
      CesiumMath.EPSILON4
    );
    expect(value[3]).toEqualEpsilon(
      1.0 / CesiumMath.PI_OVER_TWO,
      CesiumMath.EPSILON4
    );

    var longitudeRotation = attributes.longitudeRotation;
    expect(longitudeRotation.componentDatatype).toEqual(
      ComponentDatatype.FLOAT
    );
    expect(longitudeRotation.componentsPerAttribute).toEqual(1);
    expect(longitudeRotation.normalize).toEqual(false);
    value = longitudeRotation.value;
    expect(value[0]).toEqualEpsilon(0.0, CesiumMath.EPSILON4);
  });

  function checkGeometryInstanceAttributeVec3(attribute) {
    expect(attribute.componentDatatype).toEqual(ComponentDatatype.FLOAT);
    expect(attribute.componentsPerAttribute).toEqual(3);
    expect(attribute.normalize).toEqual(false);
  }

  it("provides attributes for computing texture coordinates using planes in 3D", function () {
    var attributes = smallRectangleAttributes;

    var southWest_LOW = attributes.southWest_LOW;
    var southWest_HIGH = attributes.southWest_HIGH;
    var eastward = attributes.eastward;
    var northward = attributes.northward;

    checkGeometryInstanceAttributeVec3(southWest_LOW);
    checkGeometryInstanceAttributeVec3(southWest_HIGH);
    checkGeometryInstanceAttributeVec3(eastward);
    checkGeometryInstanceAttributeVec3(northward);

    // We're using a unit sphere, so expect all HIGH values to be basically 0
    // and LOW value to be within a small cone around UNIT_X
    expect(southWest_HIGH.value[0]).toEqualEpsilon(0.0, CesiumMath.EPSILON7);
    expect(southWest_HIGH.value[1]).toEqualEpsilon(0.0, CesiumMath.EPSILON7);
    expect(southWest_HIGH.value[2]).toEqualEpsilon(0.0, CesiumMath.EPSILON7);

    expect(southWest_LOW.value[0]).toBeGreaterThan(
      Math.cos(CesiumMath.toRadians(0.2))
    );

    // Expect eastward and northward to be unit-direction vectors in the ENU coordinate system at the rectangle center
    var smallRectangleCenter = Cartographic.toCartesian(
      Rectangle.center(smallTestRectangle),
      unitSphereEllipsoid
    );
    var enuMatrix = Transforms.eastNorthUpToFixedFrame(
      smallRectangleCenter,
      unitSphereEllipsoid
    );
    var inverseEnu = Matrix4.inverse(enuMatrix, new Matrix4());

    var eastwardENU = Matrix4.multiplyByPointAsVector(
      inverseEnu,
      Cartesian3.fromArray(eastward.value),
      new Cartesian3()
    );
    eastwardENU = Cartesian3.normalize(eastwardENU, eastwardENU);
    expect(
      Cartesian3.equalsEpsilon(
        eastwardENU,
        Cartesian3.UNIT_X,
        CesiumMath.EPSILON7
      )
    ).toBe(true);

    var northwardENU = Matrix4.multiplyByPointAsVector(
      inverseEnu,
      Cartesian3.fromArray(northward.value),
      new Cartesian3()
    );
    northwardENU = Cartesian3.normalize(northwardENU, northwardENU);
    expect(
      Cartesian3.equalsEpsilon(
        northwardENU,
        Cartesian3.UNIT_Y,
        CesiumMath.EPSILON7
      )
    ).toBe(true);
  });

  it("provides attributes for computing planes in 2D and Columbus View", function () {
    var planes2D_HIGH = largeRectangleAttributes.planes2D_HIGH;
    var planes2D_LOW = largeRectangleAttributes.planes2D_LOW;

    expect(planes2D_HIGH.componentDatatype).toEqual(ComponentDatatype.FLOAT);
    expect(planes2D_HIGH.componentsPerAttribute).toEqual(4);
    expect(planes2D_HIGH.normalize).toEqual(false);

    // Because using a unit sphere expect all HIGH values to be basically 0
    var highValue = planes2D_HIGH.value;
    expect(highValue[0]).toEqualEpsilon(0.0, CesiumMath.EPSILON7);
    expect(highValue[1]).toEqualEpsilon(0.0, CesiumMath.EPSILON7);
    expect(highValue[2]).toEqualEpsilon(0.0, CesiumMath.EPSILON7);
    expect(highValue[3]).toEqualEpsilon(0.0, CesiumMath.EPSILON7);

    expect(planes2D_LOW.componentDatatype).toEqual(ComponentDatatype.FLOAT);
    expect(planes2D_LOW.componentsPerAttribute).toEqual(4);
    expect(planes2D_LOW.normalize).toEqual(false);

    var cartographic = Cartographic.fromDegrees(-45, -45, 0.0); // southwest corner
    var southwestCartesian = projection.project(cartographic);
    var lowValue = planes2D_LOW.value;
    expect(lowValue[0]).toEqualEpsilon(
      southwestCartesian.x,
      CesiumMath.EPSILON7
    );
    expect(lowValue[1]).toEqualEpsilon(
      southwestCartesian.y,
      CesiumMath.EPSILON7
    );
    expect(lowValue[2]).toEqualEpsilon(
      -southwestCartesian.y,
      CesiumMath.EPSILON7
    );
    expect(lowValue[3]).toEqualEpsilon(
      -southwestCartesian.x,
      CesiumMath.EPSILON7
    );

    // Small case
    // Because using a unit sphere expect all HIGH values to be basically 0
    highValue = smallRectangleAttributes.planes2D_HIGH.value;
    expect(highValue[0]).toEqualEpsilon(0.0, CesiumMath.EPSILON7);
    expect(highValue[1]).toEqualEpsilon(0.0, CesiumMath.EPSILON7);
    expect(highValue[2]).toEqualEpsilon(0.0, CesiumMath.EPSILON7);
    expect(highValue[3]).toEqualEpsilon(0.0, CesiumMath.EPSILON7);

    cartographic = Cartographic.fromDegrees(-0.1, -0.1, 0.0); // southwest corner
    southwestCartesian = projection.project(cartographic);
    lowValue = smallRectangleAttributes.planes2D_LOW.value;
    expect(lowValue[0]).toEqualEpsilon(
      southwestCartesian.x,
      CesiumMath.EPSILON7
    );
    expect(lowValue[1]).toEqualEpsilon(
      southwestCartesian.y,
      CesiumMath.EPSILON7
    );
    expect(lowValue[2]).toEqualEpsilon(
      -southwestCartesian.y,
      CesiumMath.EPSILON7
    );
    expect(lowValue[3]).toEqualEpsilon(
      -southwestCartesian.x,
      CesiumMath.EPSILON7
    );
  });

  it("provides attributes for rotating texture coordinates", function () {
    // 90 degree rotation of a square, so "max" in Y direction is (0,0), "max" in X direction is (1,1)
    var attributes = ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes(
      smallTestRectangle,
      [1, 0, 0, 0, 1, 1],
      unitSphereEllipsoid,
      projection,
      0.0
    );

    var uMaxVmax = attributes.uMaxVmax;
    var uvMinAndExtents = attributes.uvMinAndExtents;
    expect(uMaxVmax.componentDatatype).toEqual(ComponentDatatype.FLOAT);
    expect(uMaxVmax.componentsPerAttribute).toEqual(4);
    expect(uMaxVmax.normalize).toEqual(false);

    expect(uvMinAndExtents.componentDatatype).toEqual(ComponentDatatype.FLOAT);
    expect(uvMinAndExtents.componentsPerAttribute).toEqual(4);
    expect(uvMinAndExtents.normalize).toEqual(false);

    var value = uMaxVmax.value;
    expect(value[0]).toEqual(0.0);
    expect(value[1]).toEqual(0.0);
    expect(value[2]).toEqual(1.0);
    expect(value[3]).toEqual(1.0);

    // "min" of texture coordinates is at (1,0) and extents are just 1s
    value = uvMinAndExtents.value;
    expect(value[0]).toEqual(1.0);
    expect(value[1]).toEqual(0.0);
    expect(value[2]).toEqual(1.0);
    expect(value[3]).toEqual(1.0);
  });

  it("checks for spherical extent attributes", function () {
    expect(
      ShadowVolumeAppearance.hasAttributesForSphericalExtents(
        smallRectangleAttributes
      )
    ).toBe(false);
    expect(
      ShadowVolumeAppearance.hasAttributesForSphericalExtents(
        largeRectangleAttributes
      )
    ).toBe(true);
  });

  it("checks for planar texture coordinate attributes", function () {
    expect(
      ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes(
        smallRectangleAttributes
      )
    ).toBe(true);
    expect(
      ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes(
        largeRectangleAttributes
      )
    ).toBe(false);
  });

  it("checks if a rectangle should use spherical texture coordinates", function () {
    expect(
      ShadowVolumeAppearance.shouldUseSphericalCoordinates(smallTestRectangle)
    ).toBe(false);
    expect(
      ShadowVolumeAppearance.shouldUseSphericalCoordinates(largeTestRectangle)
    ).toBe(true);
  });

  it("creates vertex shaders for color", function () {
    // Check defines
    var sphericalTexturedAppearance = new ShadowVolumeAppearance(
      true,
      false,
      textureMaterialAppearance
    );
    var shaderSource = sphericalTexturedAppearance.createVertexShader(
      [],
      testVs,
      false,
      projection
    );
    var defines = shaderSource.defines;
    expect(defines.length).toEqual(2);
    expect(defines.indexOf("TEXTURE_COORDINATES")).not.toEqual(-1);
    expect(defines.indexOf("SPHERICAL")).not.toEqual(-1);

    // 2D variant
    shaderSource = sphericalTexturedAppearance.createVertexShader(
      [],
      testVs,
      true,
      projection
    );
    defines = shaderSource.defines;
    expect(defines.length).toEqual(6);
    expect(defines.indexOf("TEXTURE_COORDINATES")).not.toEqual(-1);
    expect(defines.indexOf("COLUMBUS_VIEW_2D")).not.toEqual(-1);

    expect(defines.indexOf(eastMostYhighDefine)).not.toEqual(-1);
    expect(defines.indexOf(eastMostYlowDefine)).not.toEqual(-1);
    expect(defines.indexOf(westMostYhighDefine)).not.toEqual(-1);
    expect(defines.indexOf(westMostYlowDefine)).not.toEqual(-1);

    // Unculled color appearance - no texcoords at all
    var sphericalUnculledColorAppearance = new ShadowVolumeAppearance(
      false,
      false,
      perInstanceColorMaterialAppearance
    );
    shaderSource = sphericalUnculledColorAppearance.createVertexShader(
      [],
      testVs,
      false,
      projection
    );
    defines = shaderSource.defines;
    expect(defines.length).toEqual(1);
    expect(defines.indexOf("PER_INSTANCE_COLOR")).not.toEqual(-1);

    // 2D variant
    shaderSource = sphericalUnculledColorAppearance.createVertexShader(
      [],
      testVs,
      true,
      projection
    );
    defines = shaderSource.defines;
    expect(defines.length).toEqual(5);

    expect(defines.indexOf(eastMostYhighDefine)).not.toEqual(-1);
    expect(defines.indexOf(eastMostYlowDefine)).not.toEqual(-1);
    expect(defines.indexOf(westMostYhighDefine)).not.toEqual(-1);
    expect(defines.indexOf(westMostYlowDefine)).not.toEqual(-1);

    expect(defines.indexOf("PER_INSTANCE_COLOR")).not.toEqual(-1);

    // Planar textured, without culling
    var planarTexturedAppearance = new ShadowVolumeAppearance(
      false,
      true,
      textureMaterialAppearance
    );
    shaderSource = planarTexturedAppearance.createVertexShader(
      [],
      testVs,
      false,
      projection
    );
    defines = shaderSource.defines;
    expect(defines.indexOf("TEXTURE_COORDINATES")).not.toEqual(-1);
    expect(defines.length).toEqual(1);

    shaderSource = planarTexturedAppearance.createVertexShader(
      [],
      testVs,
      true,
      projection
    );
    defines = shaderSource.defines;
    expect(defines.indexOf("TEXTURE_COORDINATES")).not.toEqual(-1);
    expect(defines.indexOf("COLUMBUS_VIEW_2D")).not.toEqual(-1);

    expect(defines.indexOf(eastMostYhighDefine)).not.toEqual(-1);
    expect(defines.indexOf(eastMostYlowDefine)).not.toEqual(-1);
    expect(defines.indexOf(westMostYhighDefine)).not.toEqual(-1);
    expect(defines.indexOf(westMostYlowDefine)).not.toEqual(-1);

    expect(defines.length).toEqual(6);
  });

  it("creates vertex shaders for pick", function () {
    // Check defines
    var sphericalTexturedAppearance = new ShadowVolumeAppearance(
      true,
      false,
      textureMaterialAppearance
    );
    var shaderSource = sphericalTexturedAppearance.createPickVertexShader(
      [],
      testVs,
      false,
      projection
    );
    var defines = shaderSource.defines;
    expect(defines.length).toEqual(2);
    expect(defines.indexOf("TEXTURE_COORDINATES")).not.toEqual(-1);
    expect(defines.indexOf("SPHERICAL")).not.toEqual(-1);

    // 2D variant
    shaderSource = sphericalTexturedAppearance.createPickVertexShader(
      [],
      testVs,
      true,
      projection
    );
    defines = shaderSource.defines;
    expect(defines.length).toEqual(6);
    expect(defines.indexOf("TEXTURE_COORDINATES")).not.toEqual(-1);
    expect(defines.indexOf("COLUMBUS_VIEW_2D")).not.toEqual(-1);

    expect(defines.indexOf(eastMostYhighDefine)).not.toEqual(-1);
    expect(defines.indexOf(eastMostYlowDefine)).not.toEqual(-1);
    expect(defines.indexOf(westMostYhighDefine)).not.toEqual(-1);
    expect(defines.indexOf(westMostYlowDefine)).not.toEqual(-1);

    // Unculled color appearance - no texcoords at all
    var sphericalUnculledColorAppearance = new ShadowVolumeAppearance(
      false,
      false,
      perInstanceColorMaterialAppearance
    );
    shaderSource = sphericalUnculledColorAppearance.createPickVertexShader(
      [],
      testVs,
      false,
      projection
    );
    defines = shaderSource.defines;
    expect(defines.length).toEqual(0);

    // 2D variant
    shaderSource = sphericalUnculledColorAppearance.createPickVertexShader(
      [],
      testVs,
      true,
      projection
    );
    defines = shaderSource.defines;

    expect(defines.indexOf(eastMostYhighDefine)).not.toEqual(-1);
    expect(defines.indexOf(eastMostYlowDefine)).not.toEqual(-1);
    expect(defines.indexOf(westMostYhighDefine)).not.toEqual(-1);
    expect(defines.indexOf(westMostYlowDefine)).not.toEqual(-1);

    expect(defines.length).toEqual(4);

    // Planar textured, without culling
    var planarTexturedAppearance = new ShadowVolumeAppearance(
      false,
      true,
      textureMaterialAppearance
    );
    shaderSource = planarTexturedAppearance.createPickVertexShader(
      [],
      testVs,
      false,
      projection
    );
    defines = shaderSource.defines;
    expect(defines.length).toEqual(0);

    shaderSource = planarTexturedAppearance.createPickVertexShader(
      [],
      testVs,
      true,
      projection
    );
    defines = shaderSource.defines;

    expect(defines.indexOf(eastMostYhighDefine)).not.toEqual(-1);
    expect(defines.indexOf(eastMostYlowDefine)).not.toEqual(-1);
    expect(defines.indexOf(westMostYhighDefine)).not.toEqual(-1);
    expect(defines.indexOf(westMostYlowDefine)).not.toEqual(-1);

    expect(defines.length).toEqual(4);
  });

  it("creates fragment shaders for color and pick", function () {
    // Check defines
    var sphericalTexturedAppearance = new ShadowVolumeAppearance(
      true,
      false,
      textureMaterialAppearance
    );
    var shaderSource = sphericalTexturedAppearance.createFragmentShader(false);
    var defines = shaderSource.defines;

    // Check material hookups, discard for culling, and phong shading
    expect(defines.indexOf("SPHERICAL")).not.toEqual(-1);
    expect(defines.indexOf("REQUIRES_EC")).not.toEqual(-1);
    expect(defines.indexOf("REQUIRES_WC")).not.toEqual(-1);
    expect(defines.indexOf("TEXTURE_COORDINATES")).not.toEqual(-1);
    expect(defines.indexOf("CULL_FRAGMENTS")).not.toEqual(-1);
    expect(defines.indexOf("NORMAL_EC")).not.toEqual(-1);
    expect(defines.indexOf("USES_NORMAL_EC")).not.toEqual(-1);
    expect(defines.indexOf("USES_POSITION_TO_EYE_EC")).not.toEqual(-1);
    expect(defines.indexOf("USES_TANGENT_TO_EYE")).not.toEqual(-1);
    expect(defines.indexOf("USES_ST")).not.toEqual(-1);
    expect(defines.length).toEqual(10);

    // 2D case
    shaderSource = sphericalTexturedAppearance.createFragmentShader(true);
    defines = shaderSource.defines;

    expect(defines.indexOf("REQUIRES_EC")).not.toEqual(-1);
    expect(defines.indexOf("REQUIRES_WC")).not.toEqual(-1);
    expect(defines.indexOf("TEXTURE_COORDINATES")).not.toEqual(-1);
    expect(defines.indexOf("CULL_FRAGMENTS")).not.toEqual(-1);
    expect(defines.indexOf("NORMAL_EC")).not.toEqual(-1);
    expect(defines.indexOf("USES_NORMAL_EC")).not.toEqual(-1);
    expect(defines.indexOf("USES_POSITION_TO_EYE_EC")).not.toEqual(-1);
    expect(defines.indexOf("USES_TANGENT_TO_EYE")).not.toEqual(-1);
    expect(defines.indexOf("USES_ST")).not.toEqual(-1);
    expect(defines.length).toEqual(9);

    // Culling with planar texture coordinates on a per-color material
    var planarColorAppearance = new ShadowVolumeAppearance(
      true,
      true,
      perInstanceColorMaterialAppearance
    );
    shaderSource = planarColorAppearance.createFragmentShader(false);
    defines = shaderSource.defines;

    expect(defines.indexOf("PER_INSTANCE_COLOR")).not.toEqual(-1);
    expect(defines.indexOf("REQUIRES_EC")).not.toEqual(-1);
    expect(defines.indexOf("REQUIRES_WC")).not.toEqual(-1);
    expect(defines.indexOf("TEXTURE_COORDINATES")).not.toEqual(-1);
    expect(defines.indexOf("CULL_FRAGMENTS")).not.toEqual(-1);
    expect(defines.indexOf("NORMAL_EC")).not.toEqual(-1);
    expect(defines.length).toEqual(6);

    // Pick
    shaderSource = planarColorAppearance.createPickFragmentShader(true);
    defines = shaderSource.defines;

    expect(defines.indexOf("REQUIRES_EC")).not.toEqual(-1);
    expect(defines.indexOf("REQUIRES_WC")).not.toEqual(-1);
    expect(defines.indexOf("TEXTURE_COORDINATES")).not.toEqual(-1);
    expect(defines.indexOf("CULL_FRAGMENTS")).not.toEqual(-1);
    expect(defines.indexOf("PICK")).not.toEqual(-1);
    expect(defines.length).toEqual(5);

    // Flat
    var flatSphericalTexturedAppearance = new ShadowVolumeAppearance(
      true,
      false,
      flatTextureMaterialAppearance
    );
    shaderSource = flatSphericalTexturedAppearance.createFragmentShader(false);
    defines = shaderSource.defines;
    expect(defines.indexOf("SPHERICAL")).not.toEqual(-1);
    expect(defines.indexOf("REQUIRES_EC")).not.toEqual(-1);
    expect(defines.indexOf("REQUIRES_WC")).not.toEqual(-1);
    expect(defines.indexOf("TEXTURE_COORDINATES")).not.toEqual(-1);
    expect(defines.indexOf("CULL_FRAGMENTS")).not.toEqual(-1);
    expect(defines.indexOf("NORMAL_EC")).not.toEqual(-1);
    expect(defines.indexOf("USES_NORMAL_EC")).not.toEqual(-1);
    expect(defines.indexOf("USES_POSITION_TO_EYE_EC")).not.toEqual(-1);
    expect(defines.indexOf("USES_ST")).not.toEqual(-1);
    expect(defines.indexOf("FLAT")).not.toEqual(-1);
    expect(defines.length).toEqual(10);

    var flatSphericalColorAppearance = new ShadowVolumeAppearance(
      false,
      false,
      flatPerInstanceColorMaterialAppearance
    );
    shaderSource = flatSphericalColorAppearance.createFragmentShader(false);
    defines = shaderSource.defines;
    expect(defines.indexOf("SPHERICAL")).not.toEqual(-1);
    expect(defines.indexOf("PER_INSTANCE_COLOR")).not.toEqual(-1);
    expect(defines.indexOf("FLAT")).not.toEqual(-1);
    expect(defines.length).toEqual(3);
  });
});
