PlayerWords

Atividade de adivinhação de vocabulário para o Moodle — documentação completa.

English | Português

View on GitHub Download .zip

🧪 Testes Automatizados

O PlayerWords inclui uma suíte PHPUnit cobrindo lógica de negócio, consultas ao repositório, web services e conformidade com a Privacy API. Todo push de CI executa a matriz completa (Moodle 4.5 → 5.x, PostgreSQL e MariaDB).

PHPUnit — Testes Centrais

Arquivo de teste Casos O que é coberto
backup_restore_test.php 5 Duplicar uma atividade copia suas palavras, renomeia a cópia, reconstrói o cache do curso, e não cria um item de nota duplicado — teste de regressão para a ausência de prepare_activity_structure(); uma referência a item do PlayerHUD sobrevive intacta a um “Duplicar atividade” no mesmo curso (o bloco nunca faz parte desse backup mais estreito); um backup/restauração de curso completo pra um curso novo remapeia a referência pro id novo do item, via o mapeamento playerhud_item que o próprio passo de restauração do block_playerhud registra; uma referência a item de outro curso é descartada em vez de manter apontando pro curso errado — contra o backup_controller/restore_controller real, não um atalho simulado; um backup/restauração de curso completo preserva os modos de pontuação de nota/ranking e tanto score quanto rankingpoints de uma tentativa terminada
cross_instance_security_test.php 4 Estado de sessão, busca de palavra por id, registros de tentativa e a consulta do “registro de tentativas” nunca vazam entre duas instâncias diferentes da atividade, mesmo para o mesmo estudante no mesmo curso
lib_grant_potential_test.php 6 O callback playerhud_grant_potential descoberto pelo teto de “Total XP no jogo” do próprio PlayerHUD: vazio pra uma instância de bloco não reconhecida, pra uma atividade sem item de recompensa configurado, e pra uma atividade ilimitada (reflete a mesma regra antifarm da concessão real); uma atividade limitada retorna uma linha no mesmo formato das entradas de item/missão do próprio PlayerHUD (qtd × rodadas máximas × xp do item); um item de recompensa pertencente à instância de bloco de outro curso não contribui nada; duas atividades limitadas no mesmo curso contribuem cada uma com sua própria linha
lib_reset_userdata_test.php 4 “Redefinir curso” apaga tentativas e reseta notas só quando a opção está marcada, só para o curso alvo, e o padrão do formulário vem marcado
completion/custom_completion_test.php 7 Regra de conclusão customizada (“exigir tentativas”): incompleta abaixo do limite, completa no limite, regra não reportada como disponível quando desabilitada, nomes de regra definidos, descrição inclui a quantidade exigida, ordem de exibição, uma reserva ainda pendente (rodada iniciada mas não terminada) não conta para o limite
privacy/provider_test.php 14 Declaração de metadados (incluindo a preferência de usuário “já viu a introdução”, que vale para o site inteiro); contextos por tentativas; contextos por palavras adicionadas; listar usuários no contexto (e no-op para contexto que não é de módulo); exportar dados do usuário (e no-op para lista de contextos vazia); export_user_preferences não relata nada para um usuário que nunca disparou a preferência de introdução vista, e exporta corretamente (componente, nome, descrição) uma vez que disparou; excluir dados do usuário em um único e em múltiplos contextos; excluir dados de todos os usuários num contexto (sem afetar outra atividade, e no-op para contexto que não é de módulo)
Subtotal 40  

Testes de Lógica de Negócio (tests/local/)

Arquivo de teste Casos O que é coberto
ai_word_generator_test.php 12 Parsing da resposta de IA (wrapper words/legado concepts, lista nua, cerca de código markdown removida, JSON malformado/não-array, dica cai para definition, entradas não-array ignoradas) e validação de termo como entrada não confiável (palavra única alfabética aceita; termo vazio, multi-palavra e não-alfabético rejeitados) — tudo via reflection, sem chamada real de IA
attempts_history_service_test.php 14 Registro de tentativas e nota atual do próprio estudante: vazio sem rodadas terminadas; exclui uma reserva ainda pendente; linhas exibidas da mais recente para a mais antiga, enquanto o cálculo da nota usa ordem crescente; a nota computada bate com playerwords_calculate_user_grade() para o método configurado; resumo de nota oculto em atividade não avaliada; texto da palavra cai do conceito para a palavra crua; tempo usado formatado como m:ss; a coluna de pontos no ranking aparece, formatada com 2 casas decimais, quando o ranking está ligado, e é omitida por completo quando está desligado. Relatório de todos os estudantes (get_all_history): tentativas terminadas de todos os estudantes, com o nome de cada um anexado, da mais recente para a mais antiga por padrão; exclui quem pode gerenciar a atividade, tanto das linhas do relatório quanto do dropdown de filtro; o filtro studentid restringe às linhas de um único estudante; a ordenação só aceita colunas da lista permitida, caindo para data em vez de gerar erro numa chave desconhecida; a paginação retorna fatias distintas do conjunto total de resultados
gameplay_service_test.php 19 Algoritmo de feedback por letra em 9 combinações de chute/alvo (correto, ausente, presente, letras duplicadas, esgotamento do pool); cálculo de nota para vitória, derrota e notas decimais no modo Binário; modo Linear tanto pro cálculo da nota quanto do ranking (nota cheia nas duas primeiras tentativas, escala proporcionalmente a partir da terceira, fração positiva não-zero na última tentativa permitida, degenera pra nota cheia em toda tentativa quando max_attempts é 2 ou menos, zero quando não completou)
hud_service_test.php 22 Delega pra API \block_playerhud\local\external_items do block_playerhud em toda operação de item, validando pertencimento contra a instância de bloco do próprio chamador em vez de ler as tabelas do PlayerHUD direto: localização do bloco PlayerHUD entre cursos; se o próprio plugin block_playerhud está instalado no site (bate com a checagem real de class_exists); disponibilidade por curso (verdadeiro com instância do bloco, falso sem instância, ignora instância de outro curso); resolução de nome de item (vazio pra item de outra instância de bloco); listagem de itens; consumo de itens (fundos insuficientes, sucesso, ordem FIFO, curto-circuito com quantidade zero, dispensado — não bloqueado — pra item de outra instância de bloco); concessão de itens (linhas de inventário com source='playerwords' mais XP concedido, XP retido quando a chamada sinaliza origem sem limite, item sem XP nunca altera XP em nenhum dos casos, item inexistente, item de instância estranha e quantidade zero são todos no-op)
intro_service_test.php 5 A preferência de usuário “já viu a introdução”, válida para o site inteiro: falsa por padrão; vira verdadeira e permanece assim depois de marcada (idempotente); isolada por usuário — marcar um usuário nunca afeta outro; o nome da preferência leva o prefixo Frankenstyle do plugin, o contrato do qual tanto o Privacy Provider quanto a limpeza por prefixo do db/uninstall.php dependem
ranking_service_test.php 6 Ranking vazio; ordenação decrescente por pontuação; truncamento top-5 com linha de “outsider” para o usuário atual fora do top; SEPARATEGROUPS filtra para o grupo do próprio estudante; uma reserva ainda pendente (rodada em andamento ou abandonada sem terminar) é excluída do ranking; um usuário que pode gerenciar a atividade (professor editor) nunca aparece no ranking, mesmo com tentativas próprias
round_presenter_test.php 35 Renderização das linhas da grade; texto de cooldown; mensagens de feedback (desistiu/tempo esgotado/perdeu/venceu, variando pelas tentativas usadas); contexto de ranking; contexto de resultado da rodada (em branco até terminar, revela ao terminar, cooldown reflete mudança posterior de configuração); saldo/custo em item do PlayerHUD no lobby (exibido/oculto pelo estado da rodada, início desabilitado abaixo da quantidade exigida, habilitado quando o saldo cobre), informação de temporizador no lobby; saldo/custo em item do PlayerHUD no botão de dica do painel (exibido/oculto pelo estado de revelação, dica desabilitada abaixo da quantidade exigida, habilitada quando o saldo cobre), temporizador permanece zerado antes da rodada iniciar; resumo de nota até agora (ausente antes de terminar, ausente quando não avaliada, mostra método e nota computada ao terminar, ignora uma tentativa ainda pendente); linha de informação do método de avaliação no lobby (exibida quando relevante, oculta em atividade de rodada única, oculta quando não avaliada); a tecla Ç do teclado só aparece quando o próprio banco de palavras da atividade precisa dela; contador de rodadas jogadas exibido no lobby e no resultado da rodada, usando o símbolo de infinito numa atividade ilimitada e o limite configurado nos demais casos; o rótulo de item concedido pelo PlayerHUD só aparece numa vitória de verdade com item configurado, em branco numa derrota ou quando não configurado
round_service_test.php 30 Transições de estado da rodada: palavra sorteada e round_started disparado; envio de chute (errado, correto, sem tentativas, após terminar, tamanho incompatível); desistência; timeout (termina após o prazo passar, rejeitado antes do prazo, rejeitado quando o temporizador está desabilitado); nova rodada; aviso de restrição (limite de rodadas atingido, sem restrição); count_rounds_played restrito à instância e ao usuário; cálculo de cooldown (desabilitado, sem tentativas ainda, expirado, reflete mudança posterior de configuração); recupera sorteando palavra nova após a anterior ser removida no meio da rodada; start_round reserva uma linha de tentativa; finish_round completa a reserva em vez de duplicá-la; uma rodada abandonada continua contando para max_rounds; a reserva obsoleta é descartada quando a palavra é removida no meio da rodada; uma rodada vencida concede o item do PlayerHUD configurado com XP quando max_rounds é limitado, concede sem XP quando ilimitado, e uma rodada perdida nunca concede; um custo de rodada ou dica apontando pra um item excluído, ou de outro curso, é dispensado em vez de travar o estudante pra sempre; um custo apontando pra um item só desabilitado (não excluído) continua bloqueando corretamente quando o saldo está curto — desabilitar é reversível, então o custo nunca é dispensado por causa disso
view_page_service_test.php 16 Ramificações de montagem de página: lobby fresco, palavra sorteada persiste entre chamadas, rodada terminada calcula cooldown real, aviso de restrição exibido quando o limite de rodadas é atingido; as URLs de ajuda/registro de tentativas do toolbar sempre estão presentes; a ação de desistir só aparece durante uma rodada ativa; a explicação de critério de desempate do ranking some da ajuda quando o professor desliga o ranking; a explicação do PlayerHUD na ajuda aparece assim que qualquer um entre custo de rodada, custo de dica ou recompensa por vitória está configurado; a ajuda sempre traz a dica apontando de volta pro ícone do toolbar. Introdução automática: verdadeira na primeiríssima carga de página de um usuário em qualquer atividade, e falsa dali em diante — inclusive numa atividade diferente, provando que o escopo é do site inteiro, não por instância — reproduzida igual nas ramificações de lobby fresco, rodada terminada e aviso de restrição. Status do banco de palavras: quem pode gerenciar a atividade vê a contagem de palavras ativas mesmo sem nenhum problema, e, só quando o banco realmente tem alguma, um aviso nomeado de palavra inativa (fora do intervalo de comprimento ou com caractere inválido); um estudante não vê nenhum dos dois
word_normalizer_test.php 16 Normalização sem acentuação em 8 combinações de diacríticos; is_valid_charset aceita só letras (inclusive acentuadas) e rejeita dígito, espaço, hífen, apóstrofo e string vazia, em 8 casos
words_repository_test.php 56 Seleção de palavra (pool vazio, exclusão de não aprovados/muito curtos/muito longos/caracteres não-letra, modo aleatório, determinismo e ciclagem da sequência compartilhada, evita a palavra excluída quando há alternativa, permite-a de volta quando é a única candidata); get_last_played_word_id (0 sem rodadas terminadas, ignora uma reserva pendente); inserção de palavra manual e por IA; checagem de duplicata word_exists (correspondência case-insensitive, sem correspondência, restrita à instância, corresponde independente da fonte, ignora o próprio id ao renomear); busca, atualização e exclusão de palavra restritas à instância dona; exclusão e aprovação em lote; listagem de palavras recentes com join do nome do glossário; sincronização de glossário (divisão de conceito multi-palavra, filtro de stopwords configuráveis, atualização de dica em re-sincronização sem duplicar, limpeza de órfãos quando uma entrada desaparece, modo glossaryid = 0 cobrindo todos os glossários do curso, pula um conceito cujo texto já pertence a uma palavra manual/IA sem alterar a dica dela); a tecla Ç do teclado virtual só é oferecida quando uma palavra aprovada realmente contém uma, restrito à própria atividade e ignorando palavras não aprovadas. get_fragmented_concepts relata um conceito de glossário dividido em várias palavras irmãs, exclui um conceito de palavra única, ignora palavras manuais/IA mesmo quando uma coincidentemente compartilha o texto com o próprio conceito, e é restrito à própria instância. get_inactive_words fica vazio quando não há nada errado, relata uma palavra fora do intervalo de comprimento atual e uma palavra com caractere inválido (cada uma marcada com o motivo), e ignora palavras não aprovadas. get_draw_counts fica ausente — não zero — para uma palavra nunca sorteada, soma toda tentativa independente do resultado, e é restrito à própria instância. count_glossary_candidates (a mesma prévia usada antes mesmo de a atividade existir) conta só candidatas dentro do intervalo pedido pra um glossário; glossaryid = 0 cobre todos os glossários do curso; dois conceitos que tokenizam pra mesma palavra só contam uma vez; um id de glossário de outro curso nunca é contado, mesmo sendo um id real; um curso sem nenhum glossário conta zero sem erro
Subtotal 231  

Testes de Web Services (tests/external/)

Arquivo de teste Casos O que é coberto
count_eligible_words_test.php 5 Conta só palavras aprovadas do banco cujo comprimento cai dentro do intervalo pedido; exclui palavras não aprovadas e fora do intervalo; restrito à própria instância da atividade; exige a capability mod/playerwords:addinstance (rejeita um estudante)
count_glossary_candidates_test.php 4 Conta palavras candidatas de um glossário específico dentro do intervalo de comprimento pedido; uma palavra fora do intervalo é excluída; uma stopword passada diretamente do formulário de configurações (ainda sem instância salva) remove o token correspondente antes da contagem; exige a capability mod/playerwords:addinstance (rejeita um estudante)
end_round_test.php 4 Desistência termina a rodada; timeout termina a rodada; um valor inválido de reason é rejeitado; a capability mod/playerwords:view é exigida
new_round_test.php 3 Nova rodada sorteia palavra nova; bloqueado quando o limite de rodadas já foi atingido; a capability mod/playerwords:view é exigida
reveal_hint_test.php 6 Dica é revelada; revelar duas vezes é idempotente; rejeitado após a rodada terminar; a capability mod/playerwords:view é exigida; saldo insuficiente de item do PlayerHUD (item real, válido) bloqueia a revelação; um custo apontando pra um item excluído é dispensado em vez disso
start_round_test.php 5 Cronômetro da rodada inicia; rejeitado quando já iniciado; a capability mod/playerwords:view é exigida; saldo insuficiente de item do PlayerHUD (item real, válido) bloqueia o início; um custo apontando pra um item excluído é dispensado em vez disso
submit_guess_test.php 7 Um chute errado nunca revela a palavra; um chute correto revela só quando termina; um chute perdedor também revela; a capability mod/playerwords:view é exigida; timeleft reflete os segundos restantes durante a rodada; timeleft fica congelado no momento em que a rodada terminou, não no relógio real; um total de ranking fracionado sobrevive à limpeza de retorno da API externa, contra a chamada real da webservice
Subtotal 34  
Total Geral 305  
vendor/bin/phpunit --testsuite mod_playerwords

Cobertura de linhas por classe (PHPUnit + Xdebug):

Classe Cobertura de linhas
completion\custom_completion 100%
external\count_eligible_words 70%
external\count_glossary_candidates 55%
external\end_round 76%
external\new_round 50%
external\reveal_hint 59%
external\start_round 43%
external\submit_guess 76%
local\ai_word_generator 25%
local\attempts_history_service 78%
local\gameplay_service 95%
local\hud_service 91%
local\intro_service 100%
local\ranking_service 78%
local\round_presenter 69%
local\round_service 66%
local\view_page_service 38%
local\word_normalizer 100%
local\words_repository 87%
privacy\provider 86%
Geral 63%

As quatro classes event/*.php não aparecem na lista — o Moodle só as carrega sob demanda quando o evento correspondente realmente dispara, então a instrumentação nunca chega a vê-las.