import { ArgumentScorer } from '../src/argumentScorer.js';
import { LogicManager } from '../src/logicManager.js';
import { AssumptionExtractor } from '../src/assumptionExtractor.js';
describe('ArgumentScorer', () => {
let scorer: ArgumentScorer;
let logicManager: LogicManager;
let assumptionExtractor: AssumptionExtractor;
beforeEach(() => {
logicManager = new LogicManager();
assumptionExtractor = new AssumptionExtractor(logicManager);
scorer = new ArgumentScorer(logicManager, assumptionExtractor);
});
describe('scoreArgument', () => {
it('should score a valid modus ponens argument highly', () => {
const premises = ['P', 'If P then Q'];
const conclusion = 'Q';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
// The argument is structurally valid, but scoring depends on many factors
expect(score.overallScore).toBeGreaterThanOrEqual(0);
expect(score.overallScore).toBeLessThanOrEqual(100);
expect(score).toBeDefined();
expect(score.components).toBeDefined();
});
it('should score an invalid argument lower', () => {
const premises = ['P'];
const conclusion = 'Q';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
expect(score.overallScore).toBeLessThan(60); // Should be D or F
expect(score.grade).toMatch(/[DF]/);
expect(score.components.validity.score).toBeLessThan(30);
});
it('should detect fallacies and apply penalty', () => {
const premises = ['All swans are white'];
const conclusion = 'Therefore all white things are swans';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
// Fallacy detection may or may not trigger depending on validation
expect(score).toBeDefined();
expect(score.weaknesses.length).toBeGreaterThan(0);
});
it('should assess premise plausibility', () => {
const premises = ['The sky is blue', 'Water is wet'];
const conclusion = 'Nature has properties';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
// Plausible premises should score well
expect(score.components.premisePlausibility.score).toBeGreaterThan(50);
});
it('should evaluate inference strength', () => {
const premises = ['All humans are mortal', 'Socrates is a human'];
const conclusion = 'Socrates is mortal';
const score = scorer.scoreArgument(premises, conclusion, 'syllogistic');
// Strong inference for classic syllogism
expect(score.components.inferenceStrength.score).toBeGreaterThan(70);
});
it('should assess structure quality', () => {
const premises = [
'All philosophers think',
'Socrates is a philosopher'
];
const conclusion = 'Socrates thinks';
const score = scorer.scoreArgument(premises, conclusion, 'syllogistic');
// Well-structured syllogism
expect(score.components.structureQuality.score).toBeGreaterThan(70);
});
it('should penalize complex hidden assumptions', () => {
const premises = ['It is raining'];
const conclusion = 'The ground is wet';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
// Should have assumption complexity penalty
expect(score.components.assumptionComplexity.score).toBeGreaterThan(0);
});
it('should provide strengths for good arguments', () => {
const premises = ['P implies Q', 'P'];
const conclusion = 'Q';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
// Should always have at least one strength (even if generic)
expect(score.strengths.length).toBeGreaterThan(0);
expect(score).toBeDefined();
});
it('should provide weaknesses for poor arguments', () => {
const premises = ['Some people say X'];
const conclusion = 'X is definitely true';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
expect(score.weaknesses.length).toBeGreaterThan(0);
expect(score.overallScore).toBeLessThan(50);
});
it('should provide recommendations', () => {
const premises = ['P'];
const conclusion = 'Q';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
expect(score.recommendations.length).toBeGreaterThan(0);
});
});
describe('grade conversion', () => {
it('should assign grade A for scores 90+', () => {
const premises = ['P implies Q', 'P'];
const conclusion = 'Q';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
if (score.overallScore >= 90) {
expect(score.grade).toBe('A');
}
});
it('should assign grade F for scores below 60', () => {
const premises = ['P'];
const conclusion = 'Q';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
if (score.overallScore < 60) {
expect(score.grade).toBe('F');
}
});
it('should return valid grades for all scores', () => {
const testCases = [
{ premises: ['P', 'P implies Q'], conclusion: 'Q' },
{ premises: ['P'], conclusion: 'Q' },
{ premises: ['All A are B', 'All B are C'], conclusion: 'All A are C' },
];
for (const testCase of testCases) {
const score = scorer.scoreArgument(testCase.premises, testCase.conclusion, 'propositional');
expect(['A', 'B', 'C', 'D', 'F']).toContain(score.grade);
}
});
});
describe('component scoring', () => {
it('should have all component scores between 0 and 100', () => {
const premises = ['P', 'Q'];
const conclusion = 'P and Q';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
for (const [name, component] of Object.entries(score.components)) {
expect(component.score).toBeGreaterThanOrEqual(0);
expect(component.score).toBeLessThanOrEqual(100);
}
});
it('should provide explanations for each component', () => {
const premises = ['P'];
const conclusion = 'Q';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
for (const [name, component] of Object.entries(score.components)) {
expect(component.explanation).toBeDefined();
expect(component.explanation.length).toBeGreaterThan(0);
}
});
it('should have correct weights that sum to 1', () => {
const premises = ['P'];
const conclusion = 'Q';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
let totalWeight = 0;
for (const [name, component] of Object.entries(score.components)) {
totalWeight += component.weight;
}
expect(totalWeight).toBeCloseTo(1.0, 2);
});
});
describe('comparison to average', () => {
it('should provide comparison text', () => {
const premises = ['P', 'P implies Q'];
const conclusion = 'Q';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
expect(score.comparisonToAverage).toBeDefined();
expect(score.comparisonToAverage.length).toBeGreaterThan(0);
});
it('should indicate above/below/at average appropriately', () => {
const premises = ['P', 'P implies Q'];
const conclusion = 'Q';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
const hasComparison =
score.comparisonToAverage.includes('above') ||
score.comparisonToAverage.includes('below') ||
score.comparisonToAverage.includes('average');
expect(hasComparison).toBe(true);
});
});
describe('edge cases', () => {
it('should handle empty premises', () => {
const premises: string[] = [];
const conclusion = 'Q';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
expect(score.overallScore).toBeLessThan(50);
expect(score.grade).toMatch(/[DF]/);
});
it('should handle single premise', () => {
const premises = ['P implies Q'];
const conclusion = 'Q';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
expect(score).toBeDefined();
expect(score.overallScore).toBeGreaterThanOrEqual(0);
});
it('should handle multiple premises', () => {
const premises = ['P', 'Q', 'R', 'P implies S', 'Q implies T'];
const conclusion = 'S and T';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
expect(score).toBeDefined();
expect(score.components.structureQuality).toBeDefined();
});
it('should handle complex conclusions', () => {
const premises = ['P or Q', 'not P'];
const conclusion = 'Q';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
expect(score).toBeDefined();
expect(score.overallScore).toBeGreaterThanOrEqual(0);
});
});
describe('different logical systems', () => {
it('should score propositional arguments', () => {
const premises = ['P implies Q', 'Q implies R'];
const conclusion = 'P implies R';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
expect(score).toBeDefined();
expect(score.overallScore).toBeGreaterThanOrEqual(0);
expect(score.overallScore).toBeLessThanOrEqual(100);
});
it('should score syllogistic arguments', () => {
const premises = ['All A are B', 'All B are C'];
const conclusion = 'All A are C';
const score = scorer.scoreArgument(premises, conclusion, 'syllogistic');
expect(score).toBeDefined();
expect(score.components.validity.score).toBeGreaterThan(70);
});
it('should score predicate logic arguments', () => {
const premises = ['For all x, P(x) implies Q(x)', 'P(a)'];
const conclusion = 'Q(a)';
const score = scorer.scoreArgument(premises, conclusion, 'predicate');
expect(score).toBeDefined();
expect(score.overallScore).toBeGreaterThanOrEqual(0);
expect(score.overallScore).toBeLessThanOrEqual(100);
});
it('should score modal arguments', () => {
const premises = ['Necessarily P', 'P implies Q'];
const conclusion = 'Necessarily Q';
const score = scorer.scoreArgument(premises, conclusion, 'modal');
expect(score).toBeDefined();
});
});
describe('overall score calculation', () => {
it('should produce score between 0 and 100', () => {
const testCases = [
{ premises: ['P'], conclusion: 'Q' },
{ premises: ['P', 'P implies Q'], conclusion: 'Q' },
{ premises: ['All A are B'], conclusion: 'Some A are B' },
];
for (const testCase of testCases) {
const score = scorer.scoreArgument(testCase.premises, testCase.conclusion, 'propositional');
expect(score.overallScore).toBeGreaterThanOrEqual(0);
expect(score.overallScore).toBeLessThanOrEqual(100);
}
});
it('should properly weight components', () => {
const premises = ['P', 'P implies Q'];
const conclusion = 'Q';
const score = scorer.scoreArgument(premises, conclusion, 'propositional');
// Manual calculation with weights
const calculatedScore =
score.components.validity.score * score.components.validity.weight +
score.components.premisePlausibility.score * score.components.premisePlausibility.weight +
score.components.inferenceStrength.score * score.components.inferenceStrength.weight +
score.components.structureQuality.score * score.components.structureQuality.weight -
score.components.fallacyPenalty.score * score.components.fallacyPenalty.weight -
score.components.assumptionComplexity.score * score.components.assumptionComplexity.weight;
// Should be close (rounding may differ by up to 1 point due to Math.round)
const expectedScore = Math.max(0, Math.min(100, calculatedScore));
expect(Math.abs(score.overallScore - expectedScore)).toBeLessThanOrEqual(1);
});
});
});