/* globals describe, it, beforeEach, afterEach */
"use strict";
require("should");
const sinon = require("sinon");
const NormalModule = require("../lib/NormalModule");
const NullDependency = require("../lib/dependencies/NullDependency");
const SourceMapSource = require("webpack-sources").SourceMapSource;
const OriginalSource = require("webpack-sources").OriginalSource;
const RawSource = require("webpack-sources").RawSource;

describe("NormalModule", function() {
	let normalModule;
	let request;
	let userRequest;
	let rawRequest;
	let loaders;
	let resource;
	let parser;
	beforeEach(function() {
		request = "some/request";
		userRequest = "some/userRequest";
		rawRequest = "some/rawRequest";
		loaders = [];
		resource = "some/resource";
		parser = {
			parse() {}
		};
		normalModule = new NormalModule(
			request,
			userRequest,
			rawRequest,
			loaders,
			resource,
			parser
		);
	});
	describe("#identifier", function() {
		it("returns an identifier for this module", function() {
			normalModule.identifier().should.eql(request);
		});
		it("returns an identifier from toString", function() {
			normalModule.debugId = 1000;
			normalModule.toString().should.eql("Module[1000]");
			normalModule.id = 1;
			normalModule.toString().should.eql("Module[1]");
		});
	});

	describe("#readableIdentifier", function() {
		it("calls the given requestShortener with the user request", function() {
			const spy = sinon.spy();
			normalModule.readableIdentifier({
				shorten: spy
			});
			spy.callCount.should.eql(1);
			spy.args[0][0].should.eql(userRequest);
		});
	});

	describe("#libIdent", function() {
		it("contextifies the userRequest of the module", function() {
			normalModule.libIdent({
				context: "some/context"
			}).should.eql("../userRequest");
		});
		describe("given a userRequest containing loaders", function() {
			beforeEach(function() {
				userRequest = "some/userRequest!some/other/userRequest!some/thing/is/off/here";
				normalModule = new NormalModule(
					request,
					userRequest,
					rawRequest,
					loaders,
					resource,
					parser
				);
			});
			it("contextifies every path in the userRequest", function() {
				normalModule.libIdent({
					context: "some/context"
				}).should.eql("../userRequest!../other/userRequest!../thing/is/off/here");
			});
		});
		describe("given a userRequest containing query parameters", function() {
			it("ignores paths in query parameters", function() {
				userRequest = "some/context/loader?query=foo\\bar&otherPath=testpath/other";
				normalModule = new NormalModule(
					request,
					userRequest,
					rawRequest,
					loaders,
					resource,
					parser
				);
				normalModule.libIdent({
					context: "some/context",
				}).should.eql("./loader?query=foo\\bar&otherPath=testpath/other");
			});
		});
	});

	describe("#nameForCondition", function() {
		it("return the resource", function() {
			normalModule.nameForCondition().should.eql(resource);
		});
		describe("given a resource containing a ?-sign", function() {
			const baseResource = "some/resource";
			beforeEach(function() {
				resource = baseResource + "?some=query";
				normalModule = new NormalModule(
					request,
					userRequest,
					rawRequest,
					loaders,
					resource,
					parser
				);
			});
			it("return only the part before the ?-sign", function() {
				normalModule.nameForCondition().should.eql(baseResource);
			});
		});
	});

	describe("#createSourceForAsset", function() {
		let name;
		let content;
		let sourceMap;
		beforeEach(function() {
			name = "some name";
			content = "some content";
			sourceMap = "some sourcemap";
		});
		describe("given no sourcemap", function() {
			it("returns a RawSource", function() {
				normalModule.createSourceForAsset(name, content).should.be.instanceOf(RawSource);
			});
		});
		describe("given a string as the sourcemap", function() {
			it("returns a OriginalSource", function() {
				normalModule.createSourceForAsset(name, content, sourceMap).should.be.instanceOf(OriginalSource);
			});
		});
		describe("given a some other kind of sourcemap", function() {
			beforeEach(function() {
				sourceMap = () => {};
			});
			it("returns a SourceMapSource", function() {
				normalModule.createSourceForAsset(name, content, sourceMap).should.be.instanceOf(SourceMapSource);
			});
		});
	});

	describe("#source", function() {
		describe("without the module having any source", function() {
			beforeEach(function() {
				normalModule._source = null;
			});
			it("returns a Source containing an Error", function() {
				normalModule.source().should.be.instanceOf(RawSource);
				normalModule.source().source().should.eql("throw new Error('No source available');");
			});
		});
	});

	describe("#originalSource", function() {
		let expectedSource = "some source";
		beforeEach(function() {
			normalModule._source = new RawSource(expectedSource);
		});
		it("returns an original Source", function() {
			normalModule.originalSource().should.eql(normalModule._source);
		});
	});

	describe("#updateHashWithSource", function() {
		let hashSpy;
		let hash;
		beforeEach(function() {
			hashSpy = sinon.spy();
			hash = {
				update: hashSpy
			};
		});
		describe("without the module having any source", function() {
			beforeEach(function() {
				normalModule._source = null;
			});
			it("calls hash function with \"null\"", function() {
				normalModule.updateHashWithSource(hash);
				hashSpy.callCount.should.eql(1);
				hashSpy.args[0][0].should.eql("null");
			});
		});
		describe("without the module having source", function() {
			let expectedSource = "some source";
			beforeEach(function() {
				normalModule._source = new RawSource(expectedSource);
			});
			it("calls hash function with \"source\" and then the actual source of the module", function() {
				normalModule.updateHashWithSource(hash);
				hashSpy.callCount.should.eql(2);
				hashSpy.args[0][0].should.eql("source");
				hashSpy.args[1][0].should.eql(expectedSource);
			});
		});
	});
	describe("#hasDependencies", function() {
		it("returns true if has dependencies", function() {
			normalModule.addDependency(new NullDependency());
			normalModule.hasDependencies().should.eql(true);
		});
		it("returns false if has dependencies", function() {
			normalModule.hasDependencies().should.eql(false);
		});
	});
	describe("#needRebuild", function() {
		let fileTimestamps;
		let contextTimestamps;
		let fileDependencies;
		let contextDependencies;
		let fileA;
		let fileB;

		function setDeps(
			fileDependencies,
			contextDependencies) {
			normalModule.fileDependencies = fileDependencies;
			normalModule.contextDependencies = contextDependencies;
		}

		beforeEach(function() {
			fileA = "fileA";
			fileB = "fileB";
			fileDependencies = [fileA, fileB];
			contextDependencies = [fileA, fileB];
			fileTimestamps = {
				[fileA]: 1,
				[fileB]: 1,
			};
			contextTimestamps = {
				[fileA]: 1,
				[fileB]: 1,
			};
			normalModule.buildTimestamp = 2;
			setDeps(fileDependencies, contextDependencies);
		});
		describe("given all timestamps are older than the buildTimestamp", function() {
			it("returns false", function() {
				normalModule.needRebuild(fileTimestamps, contextTimestamps).should.eql(false);
			});
		});
		describe("given a file timestamp is newer than the buildTimestamp", function() {
			beforeEach(function() {
				fileTimestamps[fileA] = 3;
			});
			it("returns true", function() {
				normalModule.needRebuild(fileTimestamps, contextTimestamps).should.eql(true);
			});
		});
		describe("given a no file timestamp exists", function() {
			beforeEach(function() {
				fileTimestamps = {};
			});
			it("returns true", function() {
				normalModule.needRebuild(fileTimestamps, contextTimestamps).should.eql(true);
			});
		});
		describe("given a context timestamp is newer than the buildTimestamp", function() {
			beforeEach(function() {
				contextTimestamps[fileA] = 3;
			});
			it("returns true", function() {
				normalModule.needRebuild(fileTimestamps, contextTimestamps).should.eql(true);
			});
		});
		describe("given a no context timestamp exists", function() {
			beforeEach(function() {
				contextTimestamps = {};
			});
			it("returns true", function() {
				normalModule.needRebuild(fileTimestamps, contextTimestamps).should.eql(true);
			});
		});
	});
	describe("#splitVariablesInUniqueNamedChunks", function() {
		let variables;
		beforeEach(function() {
			variables = [{
				name: "foo"
			}, {
				name: "bar"
			}, {
				name: "baz"
			}, {
				name: "some"
			}, {
				name: "more"
			}];
		});
		describe("given an empty array of vars", function() {
			it("returns an empty array", function() {
				normalModule.splitVariablesInUniqueNamedChunks([]).should.eql([
					[]
				]);
			});
		});
		describe("given an array of distrinct variables", function() {
			it("returns an array containing an array containing the variables", function() {
				normalModule.splitVariablesInUniqueNamedChunks(variables).should.eql([variables]);
			});
		});
		describe("given an array with duplicate variables", function() {
			it("returns several arrays each containing only distinct variable names", function() {
				normalModule.splitVariablesInUniqueNamedChunks(variables.concat(variables)).should.eql([variables, variables]);
			});
			describe("and a duplicate as the last variable", function() {
				it("returns correctly split distinct arrays", function() {
					normalModule.splitVariablesInUniqueNamedChunks(variables.concat(variables).concat(variables[0])).should.eql([variables, variables, [variables[0]]]);
				});
			});
		});
	});

	describe("#applyNoParseRule", function() {
		let rule;
		let content;
		describe("given a string as rule", function() {
			beforeEach(function() {
				rule = "some-rule";
			});
			describe("and the content starting with the string specified in rule", function() {
				beforeEach(function() {
					content = rule + "some-content";
				});
				it("returns true", function() {
					normalModule.shouldPreventParsing(rule, content).should.eql(true);
				});
			});
			describe("and the content does not start with the string specified in rule", function() {
				beforeEach(function() {
					content = "some-content";
				});
				it("returns false", function() {
					normalModule.shouldPreventParsing(rule, content).should.eql(false);
				});
			});
		});
		describe("given a regex as rule", function() {
			beforeEach(function() {
				rule = /some-rule/;
			});
			describe("and the content matches the rule", function() {
				beforeEach(function() {
					content = rule + "some-content";
				});
				it("returns true", function() {
					normalModule.shouldPreventParsing(rule, content).should.eql(true);
				});
			});
			describe("and the content does not match the rule", function() {
				beforeEach(function() {
					content = "some-content";
				});
				it("returns false", function() {
					normalModule.shouldPreventParsing(rule, content).should.eql(false);
				});
			});
		});
	});

	describe("#shouldPreventParsing", function() {
		let applyNoParseRuleSpy;
		beforeEach(function() {
			applyNoParseRuleSpy = sinon.stub();
			normalModule.applyNoParseRule = applyNoParseRuleSpy;
		});
		describe("given no noParseRule", function() {
			it("returns false", function() {
				normalModule.shouldPreventParsing().should.eql(false);
				applyNoParseRuleSpy.callCount.should.eql(0);
			});
		});
		describe("given a noParseRule", function() {
			let returnValOfSpy;
			beforeEach(function() {
				returnValOfSpy = true;
				applyNoParseRuleSpy.returns(returnValOfSpy);
			});
			describe("that is a string", function() {
				it("calls and returns whatever applyNoParseRule returns", function() {
					normalModule.shouldPreventParsing("some rule").should.eql(returnValOfSpy);
					applyNoParseRuleSpy.callCount.should.eql(1);
				});
			});
			describe("that is a regex", function() {
				it("calls and returns whatever applyNoParseRule returns", function() {
					normalModule.shouldPreventParsing("some rule").should.eql(returnValOfSpy);
					applyNoParseRuleSpy.callCount.should.eql(1);
				});
			});
			describe("that is an array", function() {
				describe("of strings and or regexs", function() {
					let someRules;
					beforeEach(function() {
						someRules = [
							"some rule",
							/some rule1/,
							"some rule2",
						];
					});
					describe("and none of them match", function() {
						beforeEach(function() {
							returnValOfSpy = false;
							applyNoParseRuleSpy.returns(returnValOfSpy);
						});
						it("returns false", function() {
							normalModule.shouldPreventParsing(someRules).should.eql(returnValOfSpy);
							applyNoParseRuleSpy.callCount.should.eql(3);
						});
					});
					describe("and the first of them matches", function() {
						beforeEach(function() {
							returnValOfSpy = true;
							applyNoParseRuleSpy.returns(returnValOfSpy);
						});
						it("returns true", function() {
							normalModule.shouldPreventParsing(someRules).should.eql(returnValOfSpy);
							applyNoParseRuleSpy.callCount.should.eql(1);
						});
					});
					describe("and the last of them matches", function() {
						beforeEach(function() {
							returnValOfSpy = true;
							applyNoParseRuleSpy.onCall(0).returns(false);
							applyNoParseRuleSpy.onCall(1).returns(false);
							applyNoParseRuleSpy.onCall(2).returns(true);
						});
						it("returns true", function() {
							normalModule.shouldPreventParsing(someRules).should.eql(returnValOfSpy);
							applyNoParseRuleSpy.callCount.should.eql(3);
						});
					});
				});
			});
		});
	});
});
